Merge remote-tracking branch 'github/master' into changing_unwrap_ref
# Conflicts: # packages/reactivity/src/ref.ts # packages/runtime-core/__tests__/apiTemplateRef.spec.ts # packages/runtime-core/src/apiWatch.ts
This commit is contained in:
commit
5ae74144f2
@ -1,37 +1,63 @@
|
|||||||
version: 2
|
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:
|
jobs:
|
||||||
build:
|
test:
|
||||||
docker:
|
<<: *defaults
|
||||||
- image: vuejs/ci
|
|
||||||
|
|
||||||
working_directory: ~/repo
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
|
- *restore_cache
|
||||||
|
- *install_deps
|
||||||
|
- *save_cache
|
||||||
|
- run: yarn ls-lint
|
||||||
|
- run: yarn test --ci --runInBand
|
||||||
|
|
||||||
- restore_cache:
|
test-dts:
|
||||||
keys:
|
<<: *defaults
|
||||||
- v1-dependencies-{{ checksum "yarn.lock" }}
|
steps:
|
||||||
- v1-dependencies-
|
- checkout
|
||||||
|
- *restore_cache
|
||||||
|
- *install_deps
|
||||||
|
- *save_cache
|
||||||
|
- run: yarn test-dts
|
||||||
|
|
||||||
- run:
|
check-size:
|
||||||
name: Install Dependencies
|
<<: *defaults
|
||||||
command: yarn --frozen-lockfile
|
steps:
|
||||||
|
- checkout
|
||||||
|
- *restore_cache
|
||||||
|
- *install_deps
|
||||||
|
- *save_cache
|
||||||
|
- run: yarn size
|
||||||
|
|
||||||
- save_cache:
|
workflows:
|
||||||
paths:
|
version: 2
|
||||||
- node_modules
|
ci:
|
||||||
- ~/.cache/yarn
|
jobs:
|
||||||
key: v1-dependencies-{{ checksum "yarn.lock" }}
|
- test
|
||||||
|
- test-dts
|
||||||
- run:
|
- check-size
|
||||||
name: Run Tests
|
|
||||||
command: yarn test --ci --runInBand
|
|
||||||
|
|
||||||
- run:
|
|
||||||
name: Test d.ts
|
|
||||||
command: yarn test-dts
|
|
||||||
|
|
||||||
- run:
|
|
||||||
name: Check size
|
|
||||||
command: yarn size
|
|
||||||
|
2
.github/FUNDING.yml
vendored
Normal file
2
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
open_collective: vuejs
|
||||||
|
patreon: evanyou
|
65
.github/contributing.md
vendored
65
.github/contributing.md
vendored
@ -19,10 +19,12 @@ Hi! I'm really excited that you are interested in contributing to Vue.js. Before
|
|||||||
- Checkout a topic branch from a base branch, e.g. `master`, and merge back against that branch.
|
- Checkout a topic branch from a base branch, e.g. `master`, and merge back against that branch.
|
||||||
|
|
||||||
- If adding a new feature:
|
- If adding a new feature:
|
||||||
|
|
||||||
- Add accompanying test case.
|
- 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.
|
- 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 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)`.
|
- 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.
|
- 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 `yarn test --coverage`.
|
||||||
@ -41,7 +43,7 @@ You will need [Node.js](http://nodejs.org) **version 10+**, and [Yarn](https://y
|
|||||||
|
|
||||||
After cloning the repo, run:
|
After cloning the repo, run:
|
||||||
|
|
||||||
``` bash
|
```bash
|
||||||
$ yarn # install the dependencies of the project
|
$ yarn # install the dependencies of the project
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -60,7 +62,7 @@ The `build` script builds all public packages (packages without `private: true`
|
|||||||
|
|
||||||
Packages to build can be specified with fuzzy matching:
|
Packages to build can be specified with fuzzy matching:
|
||||||
|
|
||||||
``` bash
|
```bash
|
||||||
# build runtime-core only
|
# build runtime-core only
|
||||||
yarn build runtime-core
|
yarn build runtime-core
|
||||||
|
|
||||||
@ -68,38 +70,64 @@ yarn build runtime-core
|
|||||||
yarn build runtime --all
|
yarn build runtime --all
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### 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:
|
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 `<script>` in the browser. The global variable exposed is specified via the `buildOptions.name` field in a package's `package.json`.
|
- **`global`**:
|
||||||
- **`esm-bundler`**: for use with bundlers like `webpack`, `rollup` and `parcel`.
|
|
||||||
- **`esm`**: for usage via native ES modules imports (in browser via `<script type="module">`, or via Node.js native ES modules support in the future)
|
- For direct use via `<script>` in the browser. The global variable exposed is specified via the `buildOptions.name` field in a package's `package.json`.
|
||||||
- **`cjs`**: for use in Node.js via `require()`.
|
- Note: global builds are not [UMD](https://github.com/umdjs/umd) builds. Instead they are built as [IIFEs](https://developer.mozilla.org/en-US/docs/Glossary/IIFE).
|
||||||
|
|
||||||
|
- **`esm-bundler`**:
|
||||||
|
|
||||||
|
- Leaves prod/dev branches with `process.env.NODE_ENV` guards (to be replaced by bundler)
|
||||||
|
- Does not ship a minified build (to be done together with the rest of the code after bundling)
|
||||||
|
- For use with bundlers like `webpack`, `rollup` and `parcel`.
|
||||||
|
- Imports dependencies (e.g. `@vue/runtime-core`, `@vue/runtime-compiler`)
|
||||||
|
- Imported dependencies are also `esm-bundler` builds and will in turn import their dependencies (e.g. `@vue/runtime-core` imports `@vue/reactivity`)
|
||||||
|
- This means you **can** install/import these deps without ending up with different instances of these dependencies
|
||||||
|
|
||||||
|
- **`esm`**:
|
||||||
|
|
||||||
|
- For usage via native ES modules imports (in browser via `<script type="module">`, or via Node.js native ES modules support in the future)
|
||||||
|
- Inlines all dependencies - i.e. it's a single ES module with no imports from other files
|
||||||
|
- This means you **must** import everything from this file and this file only to ensure you are getting the same instance of code.
|
||||||
|
- Hard-coded prod/dev branches, and the prod build is pre-minified (you will have to use different paths/aliases for dev/prod)
|
||||||
|
|
||||||
|
- **`cjs`**:
|
||||||
|
- For use in Node.js server-side rendering via `require()`.
|
||||||
|
- The dev/prod files are pre-built, but are dynamically required based on `process.env.NODE_ENV` in `index.js`, which is the default entry when you do `require('vue')`.
|
||||||
|
|
||||||
For example, to build `runtime-core` with the global build only:
|
For example, to build `runtime-core` with the global build only:
|
||||||
|
|
||||||
``` bash
|
```bash
|
||||||
yarn build runtime-core -f global
|
yarn build runtime-core -f global
|
||||||
```
|
```
|
||||||
|
|
||||||
Multiple formats can be specified as a comma-separated list:
|
Multiple formats can be specified as a comma-separated list:
|
||||||
|
|
||||||
``` bash
|
```bash
|
||||||
yarn build runtime-core -f esm,cjs
|
yarn build runtime-core -f esm,cjs
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Build with Source Maps
|
||||||
|
|
||||||
|
Use the `--sourcemap` or `-s` flag to build with source maps. Note this will make the build much slower.
|
||||||
|
|
||||||
#### Build with Type Declarations
|
#### Build with Type Declarations
|
||||||
|
|
||||||
The `--types` flag will generate type declarations during the build and in addition:
|
The `--types` or `-t` flag will generate type declarations during the build and in addition:
|
||||||
|
|
||||||
- Roll the declarations into a single `.dts` file for each package;
|
- Roll the declarations into a single `.d.ts` file for each package;
|
||||||
- Generate an API report in `<projectRoot>/temp/<packageName>.api.md`. This report contains potential warnings emitted by [api-extractor](https://api-extractor.com/).
|
- Generate an API report in `<projectRoot>/temp/<packageName>.api.md`. This report contains potential warnings emitted by [api-extractor](https://api-extractor.com/).
|
||||||
- Generate an API model json in `<projectRoot>/temp/<packageName>.api.md`. This file can be used to generate a Markdown version of the exported APIs.
|
- Generate an API model json in `<projectRoot>/temp/<packageName>.api.json`. This file can be used to generate a Markdown version of the exported APIs.
|
||||||
|
|
||||||
### `yarn dev`
|
### `yarn dev`
|
||||||
|
|
||||||
The `dev` script bundles a target package (default: `vue`) in a specified format (default: `global`) in dev mode and watches for changes. This is useful when you want to load up a build in an HTML page for quick debugging:
|
The `dev` script bundles a target package (default: `vue`) in a specified format (default: `global`) in dev mode and watches for changes. This is useful when you want to load up a build in an HTML page for quick debugging:
|
||||||
|
|
||||||
``` bash
|
```bash
|
||||||
$ yarn dev
|
$ yarn dev
|
||||||
|
|
||||||
> rollup v1.19.4
|
> rollup v1.19.4
|
||||||
@ -110,11 +138,13 @@ $ yarn dev
|
|||||||
|
|
||||||
- The `dev` script supports specifying build format via the `-f` flag just like the `build` script.
|
- The `dev` script supports specifying build format via the `-f` flag just like the `build` script.
|
||||||
|
|
||||||
|
- The `dev` script also supports the `-s` flag for generating source maps, but it will make rebuilds slower.
|
||||||
|
|
||||||
### `yarn test`
|
### `yarn test`
|
||||||
|
|
||||||
The `yarn test` script simply calls the `jest` binary, so all [Jest CLI Options](https://jestjs.io/docs/en/cli) can be used. Some examples:
|
The `yarn test` script simply calls the `jest` binary, so all [Jest CLI Options](https://jestjs.io/docs/en/cli) can be used. Some examples:
|
||||||
|
|
||||||
``` bash
|
```bash
|
||||||
# run all tests
|
# run all tests
|
||||||
$ yarn test
|
$ yarn test
|
||||||
|
|
||||||
@ -149,11 +179,13 @@ This repository employs a [monorepo](https://en.wikipedia.org/wiki/Monorepo) set
|
|||||||
|
|
||||||
- `compiler-dom`: Compiler with additional plugins specifically targeting the browser.
|
- `compiler-dom`: Compiler with additional plugins specifically targeting the browser.
|
||||||
|
|
||||||
|
- `compiler-ssr`: Compiler that produces render functions optimized for server-side rendering.
|
||||||
|
|
||||||
- `template-explorer`: A development tool for debugging compiler output. You can run `yarn dev template-explorer` and open its `index.html` to get a repl of template compilation based on current source code.
|
- `template-explorer`: A development tool for debugging compiler output. You can run `yarn dev template-explorer` and open its `index.html` to get a repl of template compilation based on current source code.
|
||||||
|
|
||||||
A [live version](https://vue-next-template-explorer.netlify.com) of the template explorer is also available, which can be used for providing reproductions for compiler bugs. You can also pick the deployment for a specific commit from the [deploy logs](https://app.netlify.com/sites/vue-next-template-explorer/deploys).
|
A [live version](https://vue-next-template-explorer.netlify.com) of the template explorer is also available, which can be used for providing reproductions for compiler bugs. You can also pick the deployment for a specific commit from the [deploy logs](https://app.netlify.com/sites/vue-next-template-explorer/deploys).
|
||||||
|
|
||||||
- `shared`: **Private.** Platform-agnostic internal utilities shared across multiple packages. This package is private and not published.
|
- `shared`: Internal utilities shared across multiple packages (especially environment-agnostic utils used by both runtime and compiler packages).
|
||||||
|
|
||||||
- `vue`: The public facing "full build" which includes both the runtime AND the compiler.
|
- `vue`: The public facing "full build" which includes both the runtime AND the compiler.
|
||||||
|
|
||||||
@ -161,20 +193,19 @@ This repository employs a [monorepo](https://en.wikipedia.org/wiki/Monorepo) set
|
|||||||
|
|
||||||
The packages can import each other directly using their package names. Note that when importing a package, the name listed in its `package.json` should be used. Most of the time the `@vue/` prefix is needed:
|
The packages can import each other directly using their package names. Note that when importing a package, the name listed in its `package.json` should be used. Most of the time the `@vue/` prefix is needed:
|
||||||
|
|
||||||
``` js
|
```js
|
||||||
import { h } from '@vue/runtime-core'
|
import { h } from '@vue/runtime-core'
|
||||||
```
|
```
|
||||||
|
|
||||||
This is made possible via several configurations:
|
This is made possible via several configurations:
|
||||||
|
|
||||||
- For TypeScript, `compilerOptions.path` in `tsconfig.json`
|
- For TypeScript, `compilerOptions.path` in `tsconfig.json`
|
||||||
- For Jest, `moduleNameMapping` in `jest.config.js`
|
- For Jest, `moduleNameMapper` in `jest.config.js`
|
||||||
- For plain Node.js, they are linked using [Yarn Workspaces](https://yarnpkg.com/blog/2017/08/02/introducing-workspaces/).
|
- For plain Node.js, they are linked using [Yarn Workspaces](https://yarnpkg.com/blog/2017/08/02/introducing-workspaces/).
|
||||||
|
|
||||||
### Package Dependencies
|
### Package Dependencies
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
+---------------------+
|
+---------------------+
|
||||||
| |
|
| |
|
||||||
| @vue/compiler-sfc |
|
| @vue/compiler-sfc |
|
||||||
|
16
.github/issue-template.md
vendored
Normal file
16
.github/issue-template.md
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<!--
|
||||||
|
IMPORTANT: Please use the following link to create a new issue:
|
||||||
|
|
||||||
|
https://new-issue.vuejs.org/?repo=vuejs/vue-next
|
||||||
|
|
||||||
|
If your issue was not created using the app above, it will be closed immediately.
|
||||||
|
|
||||||
|
中文用户请注意:
|
||||||
|
请使用上面的链接来创建新的 issue。如果不是用上述工具创建的 issue 会被自动关闭。
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Love vuejs? Please consider supporting us via Patreon or OpenCollective:
|
||||||
|
👉 https://www.patreon.com/evanyou
|
||||||
|
👉 https://opencollective.com/vuejs/donate
|
||||||
|
-->
|
7
.ls-lint.yml
Normal file
7
.ls-lint.yml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
ls:
|
||||||
|
packages/*/{src,__tests__}:
|
||||||
|
.js: kebab-case
|
||||||
|
.ts: camelCase | PascalCase
|
||||||
|
.d.ts: camelCase
|
||||||
|
.spec.ts: camelCase | PascalCase
|
||||||
|
.mock.ts: camelCase
|
537
CHANGELOG.md
Normal file
537
CHANGELOG.md
Normal file
@ -0,0 +1,537 @@
|
|||||||
|
# [3.0.0-alpha.11](https://github.com/vuejs/vue-next/compare/v3.0.0-alpha.10...v3.0.0-alpha.11) (2020-04-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **compiler:** fix pre tag whitespace handling ([7f30cb5](https://github.com/vuejs/vue-next/commit/7f30cb577257ad5765261bbffa3cae862259fcab)), closes [#908](https://github.com/vuejs/vue-next/issues/908)
|
||||||
|
* **compiler-core/slots:** should support on-component named slots ([a022b63](https://github.com/vuejs/vue-next/commit/a022b63605820c97923413ee457ba1fb69a5221e))
|
||||||
|
* **compiler-sfc:** always use offset for template block sourcemaps ([#911](https://github.com/vuejs/vue-next/issues/911)) ([db50009](https://github.com/vuejs/vue-next/commit/db5000935306214b31e33865cd57935e80e27d41))
|
||||||
|
* **inject:** allow default value to be `undefined` ([#894](https://github.com/vuejs/vue-next/issues/894)) ([94562da](https://github.com/vuejs/vue-next/commit/94562daea70fde33a340bb7b57746523c3660a8e)), closes [#892](https://github.com/vuejs/vue-next/issues/892)
|
||||||
|
* **portal:** portal should always remove its children when unmounted ([16cd8ee](https://github.com/vuejs/vue-next/commit/16cd8eee7839cc4613f17642bf37b39f7bdf1fce))
|
||||||
|
* **reactivity:** scheduled effect should not execute if stopped ([0764c33](https://github.com/vuejs/vue-next/commit/0764c33d3da8c06d472893a4e451e33394726a42)), closes [#910](https://github.com/vuejs/vue-next/issues/910)
|
||||||
|
* **runtime-core:** support attr merging on child with root level comments ([e42cb54](https://github.com/vuejs/vue-next/commit/e42cb543947d4286115b6adae6e8a5741d909f14)), closes [#904](https://github.com/vuejs/vue-next/issues/904)
|
||||||
|
* **runtime-dom:** v-cloak should be removed after compile on the root element ([#893](https://github.com/vuejs/vue-next/issues/893)) ([0ed147d](https://github.com/vuejs/vue-next/commit/0ed147d33610b86af72cbadcc4b32e6069bcaf08)), closes [#890](https://github.com/vuejs/vue-next/issues/890)
|
||||||
|
* **runtome-dom:** properly support creating customized built-in element ([b1d0b04](https://github.com/vuejs/vue-next/commit/b1d0b046afb1e8f4640d8d80b6eeaf9f89e892f7))
|
||||||
|
* **transition:** warn only when there is more than one rendered child ([#903](https://github.com/vuejs/vue-next/issues/903)) ([37b1dc8](https://github.com/vuejs/vue-next/commit/37b1dc8242608b072d14fd2a5e52f5d40829ea52))
|
||||||
|
* **types:** allow use PropType with Function ([#915](https://github.com/vuejs/vue-next/issues/915)) ([026eb72](https://github.com/vuejs/vue-next/commit/026eb729f3d1566e95f2f4253d76c20e86d1ec9b)), closes [#748](https://github.com/vuejs/vue-next/issues/748)
|
||||||
|
* **types:** export missing types from runtime-core ([#889](https://github.com/vuejs/vue-next/issues/889)) ([412ec86](https://github.com/vuejs/vue-next/commit/412ec86128fa33fa41ce435c493fd8275a785fea))
|
||||||
|
* **types/reactivity:** add generics constraint for markNonReactive ([f3b6559](https://github.com/vuejs/vue-next/commit/f3b6559408fb42ff6dc0c67001c9c67093f2b059)), closes [#917](https://github.com/vuejs/vue-next/issues/917)
|
||||||
|
|
||||||
|
|
||||||
|
### Code Refactoring
|
||||||
|
|
||||||
|
* **runtime-core:** adjust attr fallthrough behavior ([21bcdec](https://github.com/vuejs/vue-next/commit/21bcdec9435700cac98868a36716b49a7766c48d))
|
||||||
|
* rename `<portal>` to `<teleport>` ([eee5095](https://github.com/vuejs/vue-next/commit/eee50956924d7d2c916cdb8b99043da616e53af5))
|
||||||
|
* **runtime-core:** rename `createAsyncComponent` to `defineAsyncComponent` ([#888](https://github.com/vuejs/vue-next/issues/888)) ([ebc5873](https://github.com/vuejs/vue-next/commit/ebc587376ca1fb4bb8a20d4137332740605753c8))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **asyncComponent:** retry support ([c01930e](https://github.com/vuejs/vue-next/commit/c01930e60b4abf481900cdfcc2ba422890c41656))
|
||||||
|
* **compiler-core:** export `transformElement` from compiler-core ([#907](https://github.com/vuejs/vue-next/issues/907)) ([20f4965](https://github.com/vuejs/vue-next/commit/20f4965b45d410a2fe95310ecf7293b2b7f46f36))
|
||||||
|
* **compiler-core:** support v-is ([b8ffbff](https://github.com/vuejs/vue-next/commit/b8ffbffaf771c259848743cf4eb1a5ea31795aaa))
|
||||||
|
* **portal:** hydration support for portal disabled mode ([b74bab2](https://github.com/vuejs/vue-next/commit/b74bab216c3be68ab046451cf5e5b5bec5f19483))
|
||||||
|
* **portal:** SSR support for multi portal shared target ([e866434](https://github.com/vuejs/vue-next/commit/e866434f0c54498dd0fc47d48287a1d0ada36388))
|
||||||
|
* **portal:** SSR support for portal disabled prop ([9ed9bf3](https://github.com/vuejs/vue-next/commit/9ed9bf3687a770aebc265839065832761e6bafa1))
|
||||||
|
* **portal:** support disabled prop ([8ce3da0](https://github.com/vuejs/vue-next/commit/8ce3da0104db9bdd89929724c6d841ac3dfb7336))
|
||||||
|
* **portal:** support multiple portal appending to same target ([aafb880](https://github.com/vuejs/vue-next/commit/aafb880a0a9e023b62cf8fb3ae269b31f22ac84e))
|
||||||
|
* **reactivity:** add effect to public api ([#909](https://github.com/vuejs/vue-next/issues/909)) ([6fba241](https://github.com/vuejs/vue-next/commit/6fba2418507d9c65891e8d14bd63736adb377556))
|
||||||
|
* **runtime-core:** config.performance tracing support ([e93e426](https://github.com/vuejs/vue-next/commit/e93e426bfad13f40c8f1d80b8f45ac5d0926c2fc))
|
||||||
|
* **runtime-core:** emits validation and warnings ([c7c3a6a](https://github.com/vuejs/vue-next/commit/c7c3a6a3bef6275be8f9f8873358421017bb5386))
|
||||||
|
* **runtime-core:** failed component resolution should fallback to native element ([cb31eb4](https://github.com/vuejs/vue-next/commit/cb31eb4d0a0afdd2abf9e3897d9aac447dd0264b))
|
||||||
|
* **runtime-core:** support app.config.globalProperties ([27873db](https://github.com/vuejs/vue-next/commit/27873dbe1c09ac6a058d815949a4e13831513fd0))
|
||||||
|
* **runtime-core:** type and attr fallthrough support for emits option ([bf473a6](https://github.com/vuejs/vue-next/commit/bf473a64eacab21d734d556c66cc190aa4ff902d))
|
||||||
|
* **templateRef:** should work with direct reactive property ([449ab03](https://github.com/vuejs/vue-next/commit/449ab039feb10df7179898b13ecc45028a043002)), closes [#901](https://github.com/vuejs/vue-next/issues/901)
|
||||||
|
* **templateRef:** support template ref for all vnode types ([55b364d](https://github.com/vuejs/vue-next/commit/55b364decc903a6c7fccd1cdcdcfc79948c848a2))
|
||||||
|
|
||||||
|
|
||||||
|
### BREAKING CHANGES
|
||||||
|
|
||||||
|
* **runtime-core:** attribute fallthrough behavior has been adjusted
|
||||||
|
according to https://github.com/vuejs/rfcs/pull/154
|
||||||
|
* `<portal>` has been renamed to `<teleport>`.
|
||||||
|
|
||||||
|
`target` prop is also renamed to `to`, so the new usage will be:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<Teleport to="#modal-layer" :disabled="isMobile">
|
||||||
|
<div class="modal">
|
||||||
|
hello
|
||||||
|
</div>
|
||||||
|
</Teleport>
|
||||||
|
```
|
||||||
|
|
||||||
|
The primary reason for the renaming is to avoid potential naming
|
||||||
|
conflict with [native portals](https://wicg.github.io/portals/).
|
||||||
|
* **asyncComponent:** async component `error` and `loading` options have been
|
||||||
|
renamed to `errorComponent` and `loadingComponent` respectively.
|
||||||
|
* **runtime-core:** `createAsyncComponent` has been renamed to `defineAsyncComponent` for consistency with `defineComponent`.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.0.0-alpha.10](https://github.com/vuejs/vue-next/compare/v3.0.0-alpha.9...v3.0.0-alpha.10) (2020-03-24)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* fix option merge global mixins presence check ([10ad965](https://github.com/vuejs/vue-next/commit/10ad965100a88e28cb528690f2e09070fefc8872))
|
||||||
|
* **compiler-core:** assign patchFlag for template v-if fragment ([a1da9c2](https://github.com/vuejs/vue-next/commit/a1da9c28a0a7030124b1deb9369685760c67be47)), closes [#850](https://github.com/vuejs/vue-next/issues/850)
|
||||||
|
* **compiler-core:** support interpolation in RCDATA mode (e.g. textarea) ([0831b98](https://github.com/vuejs/vue-next/commit/0831b98eac344d9bdfd6f6e922902adb91ea180a))
|
||||||
|
* **keep-alive:** should update re-activated component with latest props ([1237387](https://github.com/vuejs/vue-next/commit/123738727a0af54fd632bf838dc3aa024722ee41))
|
||||||
|
* **reactivity:** should not observe frozen objects ([1b2149d](https://github.com/vuejs/vue-next/commit/1b2149dbb2dd224d01e90c1a9332bfe67aa465ce)), closes [#867](https://github.com/vuejs/vue-next/issues/867)
|
||||||
|
* **reactivity:** should not trigger map keys iteration when keys did not change ([45ba06a](https://github.com/vuejs/vue-next/commit/45ba06ac5f49876b4f05e5996f595b2c4a761f47)), closes [#877](https://github.com/vuejs/vue-next/issues/877)
|
||||||
|
* **runtime-core:** fix boolean props validation ([3b282e7](https://github.com/vuejs/vue-next/commit/3b282e7e3c96786af0a5ff61822882d1ed3f4db3))
|
||||||
|
* **runtime-dom:** invalid lineGradient svg tag ([#863](https://github.com/vuejs/vue-next/issues/863)) ([d425818](https://github.com/vuejs/vue-next/commit/d425818901428ff919a0179fc910410cbcfa119b)), closes [#862](https://github.com/vuejs/vue-next/issues/862)
|
||||||
|
* **TransitionGroup:** ignore comment node when warn (fix[#869](https://github.com/vuejs/vue-next/issues/869)) ([#875](https://github.com/vuejs/vue-next/issues/875)) ([0dba5d4](https://github.com/vuejs/vue-next/commit/0dba5d44e60d33b909f4e4d05663c7ddf746a1f2))
|
||||||
|
* do not drop SFC runtime behavior code in global builds ([4c1a193](https://github.com/vuejs/vue-next/commit/4c1a193617bee8ace6fad289b78e9d2557cb081e)), closes [#873](https://github.com/vuejs/vue-next/issues/873)
|
||||||
|
* dynamic component fallback to native element ([f529dbd](https://github.com/vuejs/vue-next/commit/f529dbde236e9eaedbded78e926951a189234f9c)), closes [#870](https://github.com/vuejs/vue-next/issues/870)
|
||||||
|
* **runtime-core:** fix component proxy props presence check ([b3890a9](https://github.com/vuejs/vue-next/commit/b3890a93e39342fd16e5fd72c59f361fc211309c)), closes [#864](https://github.com/vuejs/vue-next/issues/864)
|
||||||
|
* **suspense:** clear effects on suspense resolve ([ebc1ca8](https://github.com/vuejs/vue-next/commit/ebc1ca8eff82789987c09a9f6a934898b00153ff))
|
||||||
|
* **transition:** fix duration prop validation ([0dc2478](https://github.com/vuejs/vue-next/commit/0dc24785699101fa24d2a68786feaaac8a887520)), closes [#868](https://github.com/vuejs/vue-next/issues/868)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **asyncComponent:** SSR/hydration support for async component ([cba2f1a](https://github.com/vuejs/vue-next/commit/cba2f1aadbd0d4ae246040ecd5a91d8dd4e8fd1a))
|
||||||
|
* **runtime-core:** async component support ([c3bb316](https://github.com/vuejs/vue-next/commit/c3bb3169f497fc834654d8ae700f18b1a6613127))
|
||||||
|
* **runtime-core:** support `config.optionMergeStrategies` ([528621b](https://github.com/vuejs/vue-next/commit/528621ba41b1d7113940077574217d01d182b35f))
|
||||||
|
* add hook for transforming h's arguments ([#851](https://github.com/vuejs/vue-next/issues/851)) ([b7d1e0f](https://github.com/vuejs/vue-next/commit/b7d1e0fa2ffe4561a589580eca6e92171c311347))
|
||||||
|
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* **transform-vif:** don't need to createBlock for a component ([#853](https://github.com/vuejs/vue-next/issues/853)) ([a3601e9](https://github.com/vuejs/vue-next/commit/a3601e9fa73d10f524ed3bdf3ae44df8847c1230))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.0.0-alpha.9](https://github.com/vuejs/vue-next/compare/v3.0.0-alpha.8...v3.0.0-alpha.9) (2020-03-16)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **build:** remove __RUNTIME_COMPILE__ flag ([206640a](https://github.com/vuejs/vue-next/commit/206640a2d859a9ce9c19f22e201692f15a8d1da3)), closes [#817](https://github.com/vuejs/vue-next/issues/817)
|
||||||
|
* **compiler-core:** fix property shorthand detection ([586e5bb](https://github.com/vuejs/vue-next/commit/586e5bb8003916ba6be9b3394087df80328657f4)), closes [#845](https://github.com/vuejs/vue-next/issues/845)
|
||||||
|
* **compiler-ssr:** fix input w/ v-bind="obj" codegen ([3b40fc5](https://github.com/vuejs/vue-next/commit/3b40fc56dba56a5c1085582d11f3287e9317a151))
|
||||||
|
* **compiler-ssr:** should pass necessary tag names for dynamic v-bind ([a46f3b3](https://github.com/vuejs/vue-next/commit/a46f3b354d451a857df750a318bd0536338008cd))
|
||||||
|
* **runtime-core:** always set invalid vnode type ([#820](https://github.com/vuejs/vue-next/issues/820)) ([28a9bee](https://github.com/vuejs/vue-next/commit/28a9beed1624de9812e0f4ce9b63f7f3ed2c6db8))
|
||||||
|
* **runtime-core:** empty boolean props ([#844](https://github.com/vuejs/vue-next/issues/844)) ([c7ae269](https://github.com/vuejs/vue-next/commit/c7ae2699724bd5206ce7d2db73b86c1ef5947641)), closes [#843](https://github.com/vuejs/vue-next/issues/843)
|
||||||
|
* **runtime-core:** pass instance proxy as data() argument ([#828](https://github.com/vuejs/vue-next/issues/828)) ([d9dd1d8](https://github.com/vuejs/vue-next/commit/d9dd1d8a0ac81d7d463e0788bb2e75b2d4866db6))
|
||||||
|
* **runtime-dom:** patch xlink attribute ([#842](https://github.com/vuejs/vue-next/issues/842)) ([d318576](https://github.com/vuejs/vue-next/commit/d318576d74f8756e471942ff44d2af2a4661d775))
|
||||||
|
* simplify and use correct ctx in withCtx ([4dc8ffc](https://github.com/vuejs/vue-next/commit/4dc8ffc3788c38aff3e4c0f271d0ca111f723140))
|
||||||
|
* **runtime-core:** pass prev value to hostPatchProp ([#809](https://github.com/vuejs/vue-next/issues/809)) ([cd34603](https://github.com/vuejs/vue-next/commit/cd34603864142d5468486ec3f379679b22014a1b)), closes [#808](https://github.com/vuejs/vue-next/issues/808)
|
||||||
|
* **runtime-core:** should allow empty string and 0 as valid vnode key ([#807](https://github.com/vuejs/vue-next/issues/807)) ([54a0e93](https://github.com/vuejs/vue-next/commit/54a0e93c276f95a35b3bd6510a7f52d967fd3b7f))
|
||||||
|
* **types:** app.component should accept defineComponent return type ([#822](https://github.com/vuejs/vue-next/issues/822)) ([1e9d131](https://github.com/vuejs/vue-next/commit/1e9d1319c3f66a0a7430a4f6ac7b508486894b6b)), closes [#730](https://github.com/vuejs/vue-next/issues/730)
|
||||||
|
|
||||||
|
|
||||||
|
### Code Refactoring
|
||||||
|
|
||||||
|
* **runtime-core:** adjust patchProp value arguments order ([ca5f39e](https://github.com/vuejs/vue-next/commit/ca5f39ee3501a1d9cacdb74108318c15ee7c0abb))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **compiler-core:** wrap slot functions with render context ([ecd7ce6](https://github.com/vuejs/vue-next/commit/ecd7ce60d5234a7a0dbc11add6a690c3f9ff0617))
|
||||||
|
* **compiler-sfc:** add ssr option ([3b2d236](https://github.com/vuejs/vue-next/commit/3b2d23671409f8ac358252311bf5212882fa985a))
|
||||||
|
* **runtime-core:** add special property to get class component options ([#821](https://github.com/vuejs/vue-next/issues/821)) ([dd17fa1](https://github.com/vuejs/vue-next/commit/dd17fa1c9071b9685c379e1b12102214b757cf35))
|
||||||
|
* **runtime-core:** implement RFC-0020 ([bb7fa3d](https://github.com/vuejs/vue-next/commit/bb7fa3dabce73de63d016c75f1477e7d8bed8858))
|
||||||
|
* **runtime-core:** set context for manual slot functions as well ([8a58dce](https://github.com/vuejs/vue-next/commit/8a58dce6034944b18c2e507b5d9ab8177f60e269))
|
||||||
|
* **server-renderer:** render suspense in vnode mode ([#727](https://github.com/vuejs/vue-next/issues/727)) ([589aeb4](https://github.com/vuejs/vue-next/commit/589aeb402c58f463cc32d5e7728b56614bc9bf33))
|
||||||
|
* **ssr:** compiler-ssr support for Suspense ([80c625d](https://github.com/vuejs/vue-next/commit/80c625dce33610e53c953e9fb8fde26e3e10e358))
|
||||||
|
* **ssr:** hide comment anchors during hydration in dev mode ([cad5bcc](https://github.com/vuejs/vue-next/commit/cad5bcce40b9f2aaa520ccbd377cd5419650e55f))
|
||||||
|
* **ssr:** improve fragment mismatch handling ([60ed4e7](https://github.com/vuejs/vue-next/commit/60ed4e7e0821a2932660b87fbf8d5ca953e0e073))
|
||||||
|
* **ssr:** support getSSRProps for vnode directives ([c450ede](https://github.com/vuejs/vue-next/commit/c450ede12d1a93a70271a2fe7fcb6f8efcf1cd4c))
|
||||||
|
* **ssr/suspense:** suspense hydration ([a3cc970](https://github.com/vuejs/vue-next/commit/a3cc970030579f2c55d893d6e83bbc05324adad4))
|
||||||
|
* **types:** export `ErrorTypes` ([#840](https://github.com/vuejs/vue-next/issues/840)) ([760c3e0](https://github.com/vuejs/vue-next/commit/760c3e0fd67f6360995cdbb125f9eae4e024f3af))
|
||||||
|
|
||||||
|
|
||||||
|
### Reverts
|
||||||
|
|
||||||
|
* Revert "refactor(directives): remove binding.instance" ([2370166](https://github.com/vuejs/vue-next/commit/23701666cb487e55d05b74d66990361051715ba4))
|
||||||
|
|
||||||
|
|
||||||
|
### BREAKING CHANGES
|
||||||
|
|
||||||
|
* **runtime-core:** data no longer supports object format (per RFC-0020)
|
||||||
|
* **runtime-core:** `RendererOptions.patchProp` arguments order has changed
|
||||||
|
|
||||||
|
The `prevValue` and `nextValue` position has been swapped to keep it
|
||||||
|
consistent with other functions in the renderer implementation. This
|
||||||
|
only affects custom renderers using the `createRenderer` API.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.0.0-alpha.8](https://github.com/vuejs/vue-next/compare/v3.0.0-alpha.7...v3.0.0-alpha.8) (2020-03-06)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **directives:** ignore invalid directive hooks ([7971b04](https://github.com/vuejs/vue-next/commit/7971b0468c81483dd7026204518f7c03187d13c4)), closes [#795](https://github.com/vuejs/vue-next/issues/795)
|
||||||
|
* **portal:** fix portal placeholder text ([4397528](https://github.com/vuejs/vue-next/commit/439752822c175c737e58896e0f365f2b02bab577))
|
||||||
|
* **reactivity:** allow effect trigger inside no-track execution contexts ([274f81c](https://github.com/vuejs/vue-next/commit/274f81c5db83f0f77e1aba3240b2134a2474a72f)), closes [#804](https://github.com/vuejs/vue-next/issues/804)
|
||||||
|
* **reactivity:** Map/Set identity methods should work even if raw value contains reactive entries ([cc69fd7](https://github.com/vuejs/vue-next/commit/cc69fd72e3f9ef3572d2be40af71d22232e1b9af)), closes [#799](https://github.com/vuejs/vue-next/issues/799)
|
||||||
|
* **reactivity:** should not trigger length dependency on Array delete ([a306658](https://github.com/vuejs/vue-next/commit/a3066581f3014aae31f2d96b96428100f1674166)), closes [#774](https://github.com/vuejs/vue-next/issues/774)
|
||||||
|
* **runtime-core:** ensure inhertied attrs update on optimized child root ([6810d14](https://github.com/vuejs/vue-next/commit/6810d1402e214a12fa274ff5fb7475bad002d1b1)), closes [#677](https://github.com/vuejs/vue-next/issues/677) [#784](https://github.com/vuejs/vue-next/issues/784)
|
||||||
|
* **slots:** fix conditional slot ([3357ff4](https://github.com/vuejs/vue-next/commit/3357ff438c6ff0d4fea67923724dd3cb99ff2756)), closes [#787](https://github.com/vuejs/vue-next/issues/787)
|
||||||
|
* **ssr:** fix ssr on-the-fly compilation + slot fallback branch helper injection ([3be3785](https://github.com/vuejs/vue-next/commit/3be3785f945253918469da456a14a2d9381bcbd0))
|
||||||
|
|
||||||
|
|
||||||
|
### Code Refactoring
|
||||||
|
|
||||||
|
* **runtime-core:** adjust attr fallthrough behavior ([e1660f4](https://github.com/vuejs/vue-next/commit/e1660f4338fbf4d2a434e13193a58e00f844379b)), closes [#749](https://github.com/vuejs/vue-next/issues/749)
|
||||||
|
* **runtime-core:** revert setup() result reactive conversion ([e67f655](https://github.com/vuejs/vue-next/commit/e67f655b2687042fcc74dc0993581405abed56de))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **compiler-core:** switch to @babel/parser for expression parsing ([8449a97](https://github.com/vuejs/vue-next/commit/8449a9727c942b6049c9e577c7c15b43fdca2867))
|
||||||
|
* **compiler-ssr:** compile portal ([#775](https://github.com/vuejs/vue-next/issues/775)) ([d8ed0e7](https://github.com/vuejs/vue-next/commit/d8ed0e7fbf9bbe734667eb94e809235e79e431eb))
|
||||||
|
* **ssr:** hydration mismatch handling ([91269da](https://github.com/vuejs/vue-next/commit/91269da52c30abf6c50312555b715f5360224bb0))
|
||||||
|
|
||||||
|
|
||||||
|
### BREAKING CHANGES
|
||||||
|
|
||||||
|
* **runtime-core:** adjust attr fallthrough behavior
|
||||||
|
|
||||||
|
Updated per pending RFC https://github.com/vuejs/rfcs/pull/137
|
||||||
|
|
||||||
|
- Implicit fallthrough now by default only applies for a whitelist
|
||||||
|
of attributes (class, style, event listeners, a11y attributes, and
|
||||||
|
data attributes).
|
||||||
|
|
||||||
|
- Fallthrough is now applied regardless of whether the component has
|
||||||
|
* **runtime-core:** revert setup() result reactive conversion
|
||||||
|
|
||||||
|
Revert 6b10f0c & a840e7d. The motivation of the original change was
|
||||||
|
avoiding unnecessary deep conversions, but that can be achieved by
|
||||||
|
explicitly marking values non-reactive via `markNonReactive`.
|
||||||
|
|
||||||
|
Removing the reactive conversion behavior leads to an usability
|
||||||
|
issue in that plain objects containing refs (which is what most
|
||||||
|
composition functions will return), when exposed as a nested
|
||||||
|
property from `setup()`, will not unwrap the refs in templates. This
|
||||||
|
goes against the "no .value in template" intuition and the only
|
||||||
|
workaround requires users to manually wrap it again with `reactive()`.
|
||||||
|
|
||||||
|
So in this commit we are reverting to the previous behavior where
|
||||||
|
objects returned from `setup()` are implicitly wrapped with
|
||||||
|
`reactive()` for deep ref unwrapping.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.0.0-alpha.7](https://github.com/vuejs/vue-next/compare/v3.0.0-alpha.6...v3.0.0-alpha.7) (2020-02-26)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **renderSlot:** set slot render as a STABLE_FRAGMENT ([#776](https://github.com/vuejs/vue-next/issues/776)) ([8cb0b83](https://github.com/vuejs/vue-next/commit/8cb0b8308801159177ec16ab5a3e23672c4c1d00)), closes [#766](https://github.com/vuejs/vue-next/issues/766)
|
||||||
|
* **runtime-core:** fix slot fallback + slots typing ([4a5b91b](https://github.com/vuejs/vue-next/commit/4a5b91bd1faec76bbaa0522b095f4a07ca88a9e5)), closes [#773](https://github.com/vuejs/vue-next/issues/773)
|
||||||
|
* **runtime-core:** make watchEffect ignore deep option ([#765](https://github.com/vuejs/vue-next/issues/765)) ([19a799c](https://github.com/vuejs/vue-next/commit/19a799c28b149b14e85d9e2081fa65ed58d108ba))
|
||||||
|
* **runtime-core:** set appContext.provides to Object.create(null) ([#781](https://github.com/vuejs/vue-next/issues/781)) ([04f83fa](https://github.com/vuejs/vue-next/commit/04f83fa6810e07915e98b94c954ff0c1859aaa49))
|
||||||
|
* **template-explorer:** rename watch -> watchEffect ([#780](https://github.com/vuejs/vue-next/issues/780)) ([59393dd](https://github.com/vuejs/vue-next/commit/59393dd75766720330cb69e22086c97a392dbbe4))
|
||||||
|
* **template-ref:** fix string template refs inside slots ([3eab143](https://github.com/vuejs/vue-next/commit/3eab1438432a3bab15ccf2f6092fc3e4355f3cdd))
|
||||||
|
* **types:** ref value type unwrapping should happen at creation time ([d4c6957](https://github.com/vuejs/vue-next/commit/d4c6957e2d8ac7920a649f3a3576689cd5e1099f))
|
||||||
|
* **types:** shallowRef should not unwrap value type ([3206e5d](https://github.com/vuejs/vue-next/commit/3206e5dfe58fd0e93644d13929558d71c5171888))
|
||||||
|
|
||||||
|
|
||||||
|
### Code Refactoring
|
||||||
|
|
||||||
|
* **directives:** remove binding.instance ([52cc7e8](https://github.com/vuejs/vue-next/commit/52cc7e823148289b3dcdcb6b521984ab815fce79))
|
||||||
|
|
||||||
|
|
||||||
|
### BREAKING CHANGES
|
||||||
|
|
||||||
|
* **directives:** custom directive bindings no longer expose instance
|
||||||
|
|
||||||
|
This is a rarely used property that creates extra complexity in
|
||||||
|
ensuring it points to the correct instance. From a design
|
||||||
|
perspective, a custom directive should be scoped to the element and
|
||||||
|
data it is bound to and should not have access to the entire
|
||||||
|
instance in the first place.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.0.0-alpha.6](https://github.com/vuejs/vue-next/compare/v3.0.0-alpha.5...v3.0.0-alpha.6) (2020-02-22)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **compiler-core:** should alias name in helperString ([#743](https://github.com/vuejs/vue-next/issues/743)) ([7b987d9](https://github.com/vuejs/vue-next/commit/7b987d9450fc7befcd0946a0d53991d27ed299ec)), closes [#740](https://github.com/vuejs/vue-next/issues/740)
|
||||||
|
* **compiler-dom:** properly stringify class/style bindings when hoisting static strings ([1b9b235](https://github.com/vuejs/vue-next/commit/1b9b235663b75db040172d2ffbee1dd40b4db032))
|
||||||
|
* **reactivity:** should trigger all effects when array length is mutated ([#754](https://github.com/vuejs/vue-next/issues/754)) ([5fac655](https://github.com/vuejs/vue-next/commit/5fac65589b4455b98fd4e2f9eb3754f0acde97bb))
|
||||||
|
* **sfc:** inherit parent scopeId on child root ([#756](https://github.com/vuejs/vue-next/issues/756)) ([9547c2b](https://github.com/vuejs/vue-next/commit/9547c2b93d6d8f469314cfe055960746a3e3acbe))
|
||||||
|
* **types:** improve ref typing, close [#759](https://github.com/vuejs/vue-next/issues/759) ([627b9df](https://github.com/vuejs/vue-next/commit/627b9df4a293ae18071009d9cac7a5e995d40716))
|
||||||
|
* **types:** update setup binding unwrap types for 6b10f0c ([a840e7d](https://github.com/vuejs/vue-next/commit/a840e7ddf0b470b5da27b7b2b8b5fcf39a7197a2)), closes [#738](https://github.com/vuejs/vue-next/issues/738)
|
||||||
|
|
||||||
|
|
||||||
|
### Code Refactoring
|
||||||
|
|
||||||
|
* preserve refs in reactive arrays ([775a7c2](https://github.com/vuejs/vue-next/commit/775a7c2b414ca44d4684badb29e8e80ff6b5d3dd)), closes [#737](https://github.com/vuejs/vue-next/issues/737)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **reactivity:** expose unref and shallowRef ([e9024bf](https://github.com/vuejs/vue-next/commit/e9024bf1b7456b9cf9b913c239502593364bc773))
|
||||||
|
* **runtime-core:** add watchEffect API ([99a2e18](https://github.com/vuejs/vue-next/commit/99a2e18c9711d3d1f79f8c9c59212880efd058b9))
|
||||||
|
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* **effect:** optimize effect trigger for array length mutation ([#761](https://github.com/vuejs/vue-next/issues/761)) ([76c7f54](https://github.com/vuejs/vue-next/commit/76c7f5426919f9d29a303263bc54a1e42a66e94b))
|
||||||
|
* **reactivity:** only trigger all effects on Array length mutation if new length is shorter than old length ([33622d6](https://github.com/vuejs/vue-next/commit/33622d63600ba0f18ba4dae97bda882c918b5f7d))
|
||||||
|
|
||||||
|
|
||||||
|
### BREAKING CHANGES
|
||||||
|
|
||||||
|
* **runtime-core:** replace `watch(fn, options?)` with `watchEffect`
|
||||||
|
|
||||||
|
The `watch(fn, options?)` signature has been replaced by the new
|
||||||
|
`watchEffect` API, which has the same usage and behavior. `watch`
|
||||||
|
now only supports the `watch(source, cb, options?)` signature.
|
||||||
|
|
||||||
|
* **reactivity:** reactive arrays no longer unwraps contained refs
|
||||||
|
|
||||||
|
When reactive arrays contain refs, especially a mix of refs and
|
||||||
|
plain values, Array prototype methods will fail to function
|
||||||
|
properly - e.g. sort() or reverse() will overwrite the ref's value
|
||||||
|
instead of moving it (see #737).
|
||||||
|
|
||||||
|
Ensuring correct behavior for all possible Array methods while
|
||||||
|
retaining the ref unwrapping behavior is exceedingly complicated; In
|
||||||
|
addition, even if Vue handles the built-in methods internally, it
|
||||||
|
would still break when the user attempts to use a 3rd party utility
|
||||||
|
function (e.g. lodash) on a reactive array containing refs.
|
||||||
|
|
||||||
|
After this commit, similar to other collection types like Map and
|
||||||
|
Set, Arrays will no longer automatically unwrap contained refs.
|
||||||
|
|
||||||
|
The usage of mixed refs and plain values in Arrays should be rare in
|
||||||
|
practice. In cases where this is necessary, the user can create a
|
||||||
|
computed property that performs the unwrapping.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.0.0-alpha.5](https://github.com/vuejs/vue-next/compare/v3.0.0-alpha.4...v3.0.0-alpha.5) (2020-02-18)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **compiler:** fix v-for fragment openBlock argument ([12fcf9a](https://github.com/vuejs/vue-next/commit/12fcf9ab953acdbb8706b549c7e63f69482a495a))
|
||||||
|
* **compiler-core:** fix keep-alive when used in templates ([ade07c6](https://github.com/vuejs/vue-next/commit/ade07c64a1f98c0958e80db0458c699c21998f64)), closes [#715](https://github.com/vuejs/vue-next/issues/715)
|
||||||
|
* **compiler-core:** only check is prop on `<component>` ([78c4f32](https://github.com/vuejs/vue-next/commit/78c4f321cd0902a117c599ac705dda294fa198ed))
|
||||||
|
* **compiler-core:** relax error on unknown entities ([730d329](https://github.com/vuejs/vue-next/commit/730d329f794caf1ea2cc47628f8d74ef2d07f96e)), closes [#663](https://github.com/vuejs/vue-next/issues/663)
|
||||||
|
* **compiler-core:** should apply text transform to if branches ([e0f3c6b](https://github.com/vuejs/vue-next/commit/e0f3c6b352ab35adcad779ef0ac9670acf3d7b37)), closes [#725](https://github.com/vuejs/vue-next/issues/725)
|
||||||
|
* **compiler-core:** should not hoist element with cached + merged event handlers ([5455e8e](https://github.com/vuejs/vue-next/commit/5455e8e69a59cd1ff72330b1aed9c8e6aedc4b36))
|
||||||
|
* **compiler-dom:** fix duplicated transforms ([9e51297](https://github.com/vuejs/vue-next/commit/9e51297702f975ced1cfebad9a46afc46f0593bb))
|
||||||
|
* **compiler-sfc:** handle empty nodes with src attribute ([#695](https://github.com/vuejs/vue-next/issues/695)) ([2d56dfd](https://github.com/vuejs/vue-next/commit/2d56dfdc4fcf824bba4c0166ca5471258c4f883b))
|
||||||
|
* **compiler-ssr:** import helpers from correct packages ([8f6b669](https://github.com/vuejs/vue-next/commit/8f6b6690a2011846446804267ec49073996c3800))
|
||||||
|
* **computed:** support arrow function usage for computed option ([2fb7a63](https://github.com/vuejs/vue-next/commit/2fb7a63943d9d995248cb6d2d4fb5f22ff2ac000)), closes [#733](https://github.com/vuejs/vue-next/issues/733)
|
||||||
|
* **reactivity:** avoid cross-component dependency leaks in setup() ([d9d63f2](https://github.com/vuejs/vue-next/commit/d9d63f21b1e6f99f2fb63d736501095b131e5ad9))
|
||||||
|
* **reactivity:** effect should handle self dependency mutations ([e8e6772](https://github.com/vuejs/vue-next/commit/e8e67729cb7649d736be233b2a5e00768dd6f4ba))
|
||||||
|
* **reactivity:** trigger iteration effect on Map.set ([e1c9153](https://github.com/vuejs/vue-next/commit/e1c9153b9ed71f9b2e1ad4f9018c51d239e7dcd0)), closes [#709](https://github.com/vuejs/vue-next/issues/709)
|
||||||
|
* **runtime-core:** ensure renderCache always exists ([8383e54](https://github.com/vuejs/vue-next/commit/8383e5450e4f9679ac8a284f1c3960e3ee5b5211))
|
||||||
|
* **runtime-core:** fix keep-alive tree-shaking ([5b43764](https://github.com/vuejs/vue-next/commit/5b43764eacb59ff6ebba3195a55af4ac0cf253bb))
|
||||||
|
* **runtime-core:** fix ShapeFlags tree shaking ([0f67aa7](https://github.com/vuejs/vue-next/commit/0f67aa7da50d6ffc543754a42f1e677af11f9173))
|
||||||
|
* **runtime-core:** handle component updates with only class/style bindings ([35d91f4](https://github.com/vuejs/vue-next/commit/35d91f4e18ccb72cbf39a86fe8f39060f0bf075e))
|
||||||
|
* **runtime-core:** render context set should not unwrap reactive values ([27fbfbd](https://github.com/vuejs/vue-next/commit/27fbfbdb8beffc96134c931425f33178c23a72db))
|
||||||
|
* **runtime-core:** rework vnode hooks handling ([cfadb98](https://github.com/vuejs/vue-next/commit/cfadb98011e188114bb822ee6f678cd09ddac7e3)), closes [#684](https://github.com/vuejs/vue-next/issues/684)
|
||||||
|
* **runtime-core:** should not return early on text patchFlag ([778f3a5](https://github.com/vuejs/vue-next/commit/778f3a5e886a1a1136bc8b00b849370d7c4041be))
|
||||||
|
* **runtime-core/scheduler:** avoid duplicate updates of child component ([8a87074](https://github.com/vuejs/vue-next/commit/8a87074df013fdbb0e88f34074c2605e4af2937c))
|
||||||
|
* **runtime-core/scheduler:** invalidate job ([#717](https://github.com/vuejs/vue-next/issues/717)) ([fe9da2d](https://github.com/vuejs/vue-next/commit/fe9da2d0e4f9b338252b1b62941ee9ead71f0346))
|
||||||
|
* **runtime-core/watch:** trigger watcher with undefined as initial value ([#687](https://github.com/vuejs/vue-next/issues/687)) ([5742a0b](https://github.com/vuejs/vue-next/commit/5742a0b826fe77d2310acb530667adb758822f66)), closes [#683](https://github.com/vuejs/vue-next/issues/683)
|
||||||
|
* **runtime-dom/ssr:** properly handle xlink and boolean attributes ([e6e2c58](https://github.com/vuejs/vue-next/commit/e6e2c58234cab46fa530c383c0f7ae1cb3494da3))
|
||||||
|
* **ssr:** avoid hard-coded ssr checks in cjs builds ([bc07e95](https://github.com/vuejs/vue-next/commit/bc07e95ca84686bfa43798a444a3220581b183d8))
|
||||||
|
* **ssr:** fix class/style rendering + ssrRenderComponent export name ([688ad92](https://github.com/vuejs/vue-next/commit/688ad9239105625f7b63ac43181dfb2e9d1d4720))
|
||||||
|
* **ssr:** render components returning render function from setup ([#720](https://github.com/vuejs/vue-next/issues/720)) ([4669215](https://github.com/vuejs/vue-next/commit/4669215ca2f82d90a1bd730613259f3167e199cd))
|
||||||
|
* **transition-group:** handle multiple move-classes ([#679](https://github.com/vuejs/vue-next/issues/679)) ([5495c70](https://github.com/vuejs/vue-next/commit/5495c70c4a3f740ef4ac575ffee5466ca747cca1)), closes [#678](https://github.com/vuejs/vue-next/issues/678)
|
||||||
|
* **types:** app.component should accept defineComponent return type ([57ee5df](https://github.com/vuejs/vue-next/commit/57ee5df364f03816e548f4f3bf05edc7a089c362)), closes [#730](https://github.com/vuejs/vue-next/issues/730)
|
||||||
|
* **types:** ensure correct oldValue typing based on lazy option ([c6a9787](https://github.com/vuejs/vue-next/commit/c6a9787941ca99877d268182a5bb57fcf8b80b75)), closes [#719](https://github.com/vuejs/vue-next/issues/719)
|
||||||
|
* **v-on:** transform click.right and click.middle modifiers ([028f748](https://github.com/vuejs/vue-next/commit/028f748c32f80842be39897fdacc37f6700f00a7)), closes [#735](https://github.com/vuejs/vue-next/issues/735)
|
||||||
|
* remove effect from public API ([4bc4cb9](https://github.com/vuejs/vue-next/commit/4bc4cb970f7a65177948c5d817bb43ecb0324636)), closes [#712](https://github.com/vuejs/vue-next/issues/712)
|
||||||
|
* **v-model:** should use dynamic directive on input with dynamic v-bind ([1f2de9e](https://github.com/vuejs/vue-next/commit/1f2de9e232409b09c97b67d0824d1450beed6eb1))
|
||||||
|
|
||||||
|
|
||||||
|
### Code Refactoring
|
||||||
|
|
||||||
|
* **watch:** adjust watch API behavior ([9571ede](https://github.com/vuejs/vue-next/commit/9571ede84bb6949e13c25807cc8f016ace29dc8a))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **compiler:** mark hoisted trees with patchFlag ([175f8aa](https://github.com/vuejs/vue-next/commit/175f8aae8d009e044e3674f7647bf1397f3a794a))
|
||||||
|
* **compiler:** warn invalid children for transition and keep-alive ([4cc39e1](https://github.com/vuejs/vue-next/commit/4cc39e14a297f42230f5aac5ec08e3c98902b98d))
|
||||||
|
* **compiler-core:** support mode: cjs in codegen ([04da2a8](https://github.com/vuejs/vue-next/commit/04da2a82e8fbde2b60b2392bc4bdcc5e61113202))
|
||||||
|
* **compiler-core/v-on:** support [@vnode-xxx](https://github.com/vnode-xxx) usage for vnode hooks ([571ed42](https://github.com/vuejs/vue-next/commit/571ed4226be618dcc9f95e4c2da8d82d7d2f7750))
|
||||||
|
* **compiler-dom:** handle constant expressions when stringifying static content ([8b7c162](https://github.com/vuejs/vue-next/commit/8b7c162125cb72068727a76ede8afa2896251db0))
|
||||||
|
* **compiler-dom/runtime-dom:** stringify eligible static trees ([27913e6](https://github.com/vuejs/vue-next/commit/27913e661ac551f580bd5fd42b49fe55cbe8dbb8))
|
||||||
|
* **reactivity:** add shallowReactive function ([#689](https://github.com/vuejs/vue-next/issues/689)) ([7f38c1e](https://github.com/vuejs/vue-next/commit/7f38c1e0ff5a7591f67ed21aa3a2944db2e72a27))
|
||||||
|
* **runtime-core/reactivity:** expose shallowReactive ([#711](https://github.com/vuejs/vue-next/issues/711)) ([21944c4](https://github.com/vuejs/vue-next/commit/21944c4a42a65f20245794fa5f07add579b7121f))
|
||||||
|
* **server-renderer:** support on-the-fly template compilation ([#707](https://github.com/vuejs/vue-next/issues/707)) ([6d10a6c](https://github.com/vuejs/vue-next/commit/6d10a6c77242aec98103f15d6cb672ba63c18abf))
|
||||||
|
* **ssr:** render portals ([#714](https://github.com/vuejs/vue-next/issues/714)) ([e495fa4](https://github.com/vuejs/vue-next/commit/e495fa4a1872d03ed59252e7ed5dd2b708adb7ae))
|
||||||
|
* **ssr:** support portal hydration ([70dc3e3](https://github.com/vuejs/vue-next/commit/70dc3e3ae74f08d53243e6f078794c16f359e272))
|
||||||
|
* **ssr:** useSSRContext ([fd03149](https://github.com/vuejs/vue-next/commit/fd031490fb89b7c0d1d478b586151a24324101a3))
|
||||||
|
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* prevent renderer hot functions being inlined by minifiers ([629ee75](https://github.com/vuejs/vue-next/commit/629ee75588fc2ca4ab2b3786046f788d3547b6bc))
|
||||||
|
* **reactivity:** better computed tracking ([#710](https://github.com/vuejs/vue-next/issues/710)) ([8874b21](https://github.com/vuejs/vue-next/commit/8874b21a7e2383a8bb6c15a7095c1853aa5ae705))
|
||||||
|
|
||||||
|
|
||||||
|
### BREAKING CHANGES
|
||||||
|
|
||||||
|
* **watch:** `watch` behavior has been adjusted.
|
||||||
|
|
||||||
|
- When using the `watch(source, callback, options?)` signature, the
|
||||||
|
callback now fires lazily by default (consistent with 2.x
|
||||||
|
behavior).
|
||||||
|
|
||||||
|
Note that the `watch(effect, options?)` signature is still eager,
|
||||||
|
since it must invoke the `effect` immediately to collect
|
||||||
|
dependencies.
|
||||||
|
|
||||||
|
- The `lazy` option has been replaced by the opposite `immediate`
|
||||||
|
option, which defaults to `false`. (It's ignored when using the
|
||||||
|
effect signature)
|
||||||
|
|
||||||
|
- Due to the above changes, the `watch` option in Options API now
|
||||||
|
behaves exactly the same as 2.x.
|
||||||
|
|
||||||
|
- When using the effect signature or `{ immediate: true }`, the
|
||||||
|
initial execution is now performed synchronously instead of
|
||||||
|
deferred until the component is mounted. This is necessary for
|
||||||
|
certain use cases to work properly with `async setup()` and
|
||||||
|
Suspense.
|
||||||
|
|
||||||
|
The side effect of this is the immediate watcher invocation will
|
||||||
|
no longer have access to the mounted DOM. However, the watcher can
|
||||||
|
be initiated inside `onMounted` to retain previous behavior.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.0.0-alpha.4](https://github.com/vuejs/vue-next/compare/v3.0.0-alpha.3...v3.0.0-alpha.4) (2020-01-27)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **reactivity:** Array methods relying on identity should work with raw values ([aefb7d2](https://github.com/vuejs/vue-next/commit/aefb7d282ed716923ca1a288a63a83a94af87ebc))
|
||||||
|
* **runtime-core:** instance should not expose non-declared props ([2884831](https://github.com/vuejs/vue-next/commit/2884831065e16ccf5bd3ae1ee95116803ee3b18c))
|
||||||
|
* **runtime-dom:** should not access document in non-browser env ([48152bc](https://github.com/vuejs/vue-next/commit/48152bc88ea817ae23e2987dce99d64b426366c1)), closes [#657](https://github.com/vuejs/vue-next/issues/657)
|
||||||
|
* **v-model/emit:** update:camelCase events should trigger kebab case equivalent ([2837ce8](https://github.com/vuejs/vue-next/commit/2837ce842856d51dfbb55e3fa4a36a352446fb54)), closes [#656](https://github.com/vuejs/vue-next/issues/656)
|
||||||
|
|
||||||
|
|
||||||
|
### Code Refactoring
|
||||||
|
|
||||||
|
* adjust `createApp` related API signatures ([c07751f](https://github.com/vuejs/vue-next/commit/c07751fd3605f301dc0f02fd2a48acc7ba7a0397))
|
||||||
|
* remove implicit reactive() call on renderContext ([6b10f0c](https://github.com/vuejs/vue-next/commit/6b10f0cd1da942c1d96746672b5f595df7d125b5))
|
||||||
|
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* **ssr:** avoid unnecessary async overhead ([297282a](https://github.com/vuejs/vue-next/commit/297282a81259289bfed207d0c9393337aea70117))
|
||||||
|
|
||||||
|
|
||||||
|
### BREAKING CHANGES
|
||||||
|
|
||||||
|
* object returned from `setup()` are no longer implicitly
|
||||||
|
passed to `reactive()`.
|
||||||
|
|
||||||
|
The renderContext is the object returned by `setup()` (or a new object
|
||||||
|
if no setup() is present). Before this change, it was implicitly passed
|
||||||
|
to `reactive()` for ref unwrapping. But this has the side effect of
|
||||||
|
unnecessary deep reactive conversion on properties that should not be
|
||||||
|
made reactive (e.g. computed return values and injected non-reactive
|
||||||
|
objects), and can lead to performance issues.
|
||||||
|
|
||||||
|
This change removes the `reactive()` call and instead performs a
|
||||||
|
shallow ref unwrapping at the render proxy level. The breaking part is
|
||||||
|
when the user returns an object with a plain property from `setup()`,
|
||||||
|
e.g. `return { count: 0 }`, this property will no longer trigger
|
||||||
|
updates when mutated by a in-template event handler. Instead, explicit
|
||||||
|
refs are required.
|
||||||
|
|
||||||
|
This also means that any objects not explicitly made reactive in
|
||||||
|
`setup()` will remain non-reactive. This can be desirable when
|
||||||
|
exposing heavy external stateful objects on `this`.
|
||||||
|
* `createApp` API has been adjusted.
|
||||||
|
|
||||||
|
- `createApp()` now accepts the root component, and optionally a props
|
||||||
|
object to pass to the root component.
|
||||||
|
- `app.mount()` now accepts a single argument (the root container)
|
||||||
|
- `app.unmount()` no longer requires arguments.
|
||||||
|
|
||||||
|
New behavior looks like the following:
|
||||||
|
|
||||||
|
``` js
|
||||||
|
const app = createApp(RootComponent)
|
||||||
|
app.mount('#app')
|
||||||
|
app.unmount()
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.0.0-alpha.3](https://github.com/vuejs/vue-next/compare/v3.0.0-alpha.2...v3.0.0-alpha.3) (2020-01-22)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Suspense should include into dynamic children ([#653](https://github.com/vuejs/vue-next/issues/653)) ([ec63623](https://github.com/vuejs/vue-next/commit/ec63623fe8d395e1cd759f27b90b1ccc1b616931)), closes [#649](https://github.com/vuejs/vue-next/issues/649)
|
||||||
|
* **compiler-core:** avoid override user keys when injecting branch key ([#630](https://github.com/vuejs/vue-next/issues/630)) ([aca2c2a](https://github.com/vuejs/vue-next/commit/aca2c2a81e2793befce516378a02afd1e4da3d3d))
|
||||||
|
* **compiler-core:** force `<svg>` into blocks for correct runtime isSVG ([f2ac28b](https://github.com/vuejs/vue-next/commit/f2ac28b31e9f1e8ebcd68ca9a1e8ea29653b0916))
|
||||||
|
* **compiler-sfc:** only transform relative asset URLs ([#628](https://github.com/vuejs/vue-next/issues/628)) ([c71ca35](https://github.com/vuejs/vue-next/commit/c71ca354b9368135b55676c5817cebffaf3fd9c5))
|
||||||
|
* **dom:** fix `<svg>` and `<foreignObject>` mount and updates ([4f06eeb](https://github.com/vuejs/vue-next/commit/4f06eebc1c2a29d0e4165c6e87f849732ec2cd0f))
|
||||||
|
* **runtime-core:** condition for parent node check should be any different nodes ([c35fea3](https://github.com/vuejs/vue-next/commit/c35fea3d608acbb571ace6693284061e1cadf7ba)), closes [#622](https://github.com/vuejs/vue-next/issues/622)
|
||||||
|
* **runtime-core:** isSVG check should also apply for patch branch ([035b656](https://github.com/vuejs/vue-next/commit/035b6560f7eb64ce940ed0d06e19086ad9a3890f)), closes [#639](https://github.com/vuejs/vue-next/issues/639)
|
||||||
|
* **runtime-core:** should not warn unused attrs when accessed via setup context ([751d838](https://github.com/vuejs/vue-next/commit/751d838fb963e580a40df2d84840ba2198480185)), closes [#625](https://github.com/vuejs/vue-next/issues/625)
|
||||||
|
* **transition:** handle multiple transition classes ([#638](https://github.com/vuejs/vue-next/issues/638)) ([#645](https://github.com/vuejs/vue-next/issues/645)) ([98d50d8](https://github.com/vuejs/vue-next/commit/98d50d874dcb32a246216b936e442e5b95ab4825))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **runtime-core:** emit now returns array of return values from all triggered handlers ([e81c8a3](https://github.com/vuejs/vue-next/commit/e81c8a32c7b66211cbaecffa93efd4629ec45ad9)), closes [#635](https://github.com/vuejs/vue-next/issues/635)
|
||||||
|
* **runtime-core:** support app.unmount(container) ([#601](https://github.com/vuejs/vue-next/issues/601)) ([04ac6c4](https://github.com/vuejs/vue-next/commit/04ac6c467a4122877c204d7494c86f89498d2dc6)), closes [#593](https://github.com/vuejs/vue-next/issues/593)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.0.0-alpha.2](https://github.com/vuejs/vue-next/compare/v3.0.0-alpha.1...v3.0.0-alpha.2) (2020-01-13)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **compiler/v-on:** handle multiple statements in v-on handler (close [#572](https://github.com/vuejs/vue-next/issues/572)) ([137893a](https://github.com/vuejs/vue-next/commit/137893a4fdd3d2b901adca31e30d916df925b108))
|
||||||
|
* **compiler/v-slot:** handle implicit default slot mixed with named slots ([2ac4b72](https://github.com/vuejs/vue-next/commit/2ac4b723e010082488b5be64af73e41c9677a28d))
|
||||||
|
* **reactivity:** should delete observe value ([#598](https://github.com/vuejs/vue-next/issues/598)) ([63a6563](https://github.com/vuejs/vue-next/commit/63a656310676e3927b2e57d813fd6300c0a42590)), closes [#597](https://github.com/vuejs/vue-next/issues/597)
|
||||||
|
* **runtime-core:** allow classes to be passed as plugins ([#588](https://github.com/vuejs/vue-next/issues/588)) ([8f616a8](https://github.com/vuejs/vue-next/commit/8f616a89c580bc211540d5e4d60488ff24d024cc))
|
||||||
|
* **runtime-core:** should preserve props casing when component has no declared props ([bb6a346](https://github.com/vuejs/vue-next/commit/bb6a346996ce0bf05596c605ba5ddbe0743ef84b)), closes [#583](https://github.com/vuejs/vue-next/issues/583)
|
||||||
|
* **runtime-core/renderer:** fix v-if toggle inside blocks ([2e9726e](https://github.com/vuejs/vue-next/commit/2e9726e6a219d546cd28e4ed42be64719708f047)), closes [#604](https://github.com/vuejs/vue-next/issues/604) [#607](https://github.com/vuejs/vue-next/issues/607)
|
||||||
|
* **runtime-core/vnode:** should not render boolean values in vnode children (close [#574](https://github.com/vuejs/vue-next/issues/574)) ([84dc5a6](https://github.com/vuejs/vue-next/commit/84dc5a686275528733977ea1570e0a892ba3e177))
|
||||||
|
* **types:** components options should accept components defined with defineComponent ([#602](https://github.com/vuejs/vue-next/issues/602)) ([74baea1](https://github.com/vuejs/vue-next/commit/74baea108aa93377c4959f9a6b8bc8f9548700ba))
|
||||||
|
* **watch:** remove recorded effect on manual stop ([#590](https://github.com/vuejs/vue-next/issues/590)) ([453e688](https://github.com/vuejs/vue-next/commit/453e6889da22e7224b638261a32438bdf5c62e41))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.0.0-alpha.1](https://github.com/vuejs/vue-next/compare/v3.0.0-alpha.0...v3.0.0-alpha.1) (2020-01-02)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **runtime-core:** pass options to plugins ([#561](https://github.com/vuejs/vue-next/issues/561)) ([4d20981](https://github.com/vuejs/vue-next/commit/4d20981eb069b20e1627916b977aedb2d68eca86))
|
||||||
|
* **sfc:** treat custom block content as raw text ([d6275a3](https://github.com/vuejs/vue-next/commit/d6275a3c310e6e9426f897afe35ff6cdb125c023))
|
||||||
|
* mounting new children ([7d436ab](https://github.com/vuejs/vue-next/commit/7d436ab59a30562a049e199ae579df7ac8066829))
|
||||||
|
* **core:** clone mounted hoisted vnodes on patch ([47a6a84](https://github.com/vuejs/vue-next/commit/47a6a846311203fa59584486265f5da387afa51d))
|
||||||
|
* **fragment:** perform direct remove when removing fragments ([2fdb499](https://github.com/vuejs/vue-next/commit/2fdb499bd96b4d1a8a7a1964d59e8dc5dacd9d22))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **hmr:** root instance reload ([eda495e](https://github.com/vuejs/vue-next/commit/eda495efd824f17095728a4d2a6db85ca874e5ca))
|
||||||
|
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* **compiler-core:** simplify `advancePositionWithMutation` ([#564](https://github.com/vuejs/vue-next/issues/564)) ([ad2a0bd](https://github.com/vuejs/vue-next/commit/ad2a0bde988de743d4abc62b681b6a4888545a51))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.0.0-alpha.0](https://github.com/vuejs/vue-next/compare/a8522cf48c09efbb2063f129cf1bea0dae09f10a...v3.0.0-alpha.0) (2019-12-20)
|
||||||
|
|
||||||
|
For changes between 2.x and 3.0 up to this release, please refer to merged RFCs [here](https://github.com/vuejs/rfcs/pulls?q=is%3Apr+is%3Amerged+label%3A3.x).
|
28
README.md
28
README.md
@ -1,32 +1,14 @@
|
|||||||
# vue-next [](https://circleci.com/gh/vuejs/vue-next)
|
# vue-next [](https://circleci.com/gh/vuejs/vue-next)
|
||||||
|
|
||||||
## Status: Pre-Alpha.
|
## Status: Alpha.
|
||||||
|
|
||||||
We have achieved most of the architectural goals and new features planned for v3:
|
The current codebase has reached feature parity with v2.x (except for features explicitly removed by RFCs), and has landed all the changes proposed in [merged RFCs](https://github.com/vuejs/rfcs/pulls?q=is%3Apr+is%3Amerged+label%3A3.x). We are ready to advance to beta phase once we resolve a number of pending RFCs.
|
||||||
|
|
||||||
- Compiler
|
There is a simple webpack-based setup with Single-File Component support available [here](https://github.com/vuejs/vue-next-webpack-preview).
|
||||||
- [x] Modular architecture
|
|
||||||
- [x] "Block tree" optimization
|
|
||||||
- [x] More aggressive static tree hoisting
|
|
||||||
- [x] Source map support
|
|
||||||
- [x] Built-in identifier prefixing (aka "stripWith")
|
|
||||||
- [x] Built-in pretty-printing
|
|
||||||
- [x] Lean ~10kb brotli-compressed browser build after dropping source map and identifier prefixing
|
|
||||||
|
|
||||||
- Runtime
|
Please note that there could still be undocumented behavior inconsistencies with 2.x. When you run into such a case, please make sure to first check if the behavior difference has already been proposed in an existing RFC. If the inconsistency is not part of an RFC, then it's likely unintended, and an issue should be opened (please make sure to use the [issue helper](https://new-issue.vuejs.org/?repo=vuejs/vue-next) when opening new issues).
|
||||||
- [x] Significantly faster
|
|
||||||
- [x] Simultaneous Composition API + Options API support, **with typings**
|
|
||||||
- [x] Proxy-based change detection
|
|
||||||
- [x] Fragments
|
|
||||||
- [x] Portals
|
|
||||||
- [x] Suspense w/ `async setup()`
|
|
||||||
|
|
||||||
## Major TODOs:
|
In addition, the current implementation requires native ES2015+ in the runtime environment and does not support IE11 (yet). The IE11 compatible build will be worked on after we have reached RC stage.
|
||||||
|
|
||||||
- [ ] SFC compiler
|
|
||||||
- [ ] Server-side rendering
|
|
||||||
|
|
||||||
Also note that the current implementation requires native ES2015+ in the runtime environment and does not support IE11 (yet).
|
|
||||||
|
|
||||||
## Contribution
|
## Contribution
|
||||||
|
|
||||||
|
@ -5,8 +5,10 @@ module.exports = {
|
|||||||
__TEST__: true,
|
__TEST__: true,
|
||||||
__VERSION__: require('./package.json').version,
|
__VERSION__: require('./package.json').version,
|
||||||
__BROWSER__: false,
|
__BROWSER__: false,
|
||||||
__BUNDLER__: false,
|
__BUNDLER__: true,
|
||||||
__RUNTIME_COMPILE__: true,
|
__RUNTIME_COMPILE__: true,
|
||||||
|
__GLOBAL__: false,
|
||||||
|
__NODE_JS__: true,
|
||||||
__FEATURE_OPTIONS__: true,
|
__FEATURE_OPTIONS__: true,
|
||||||
__FEATURE_SUSPENSE__: true
|
__FEATURE_SUSPENSE__: true
|
||||||
},
|
},
|
||||||
@ -16,13 +18,20 @@ module.exports = {
|
|||||||
'packages/*/src/**/*.ts',
|
'packages/*/src/**/*.ts',
|
||||||
'!packages/runtime-test/src/utils/**',
|
'!packages/runtime-test/src/utils/**',
|
||||||
'!packages/template-explorer/**',
|
'!packages/template-explorer/**',
|
||||||
'!packages/size-check/**'
|
'!packages/size-check/**',
|
||||||
|
'!packages/runtime-core/src/profiling.ts'
|
||||||
],
|
],
|
||||||
watchPathIgnorePatterns: ['/node_modules/'],
|
watchPathIgnorePatterns: ['/node_modules/', '/dist/', '/.git/'],
|
||||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'json'],
|
moduleFileExtensions: ['ts', 'tsx', 'js', 'json'],
|
||||||
moduleNameMapper: {
|
moduleNameMapper: {
|
||||||
'^@vue/(.*?)$': '<rootDir>/packages/$1/src'
|
'^@vue/(.*?)$': '<rootDir>/packages/$1/src',
|
||||||
|
vue: '<rootDir>/packages/vue/src'
|
||||||
},
|
},
|
||||||
rootDir: __dirname,
|
rootDir: __dirname,
|
||||||
testMatch: ['<rootDir>/packages/**/__tests__/**/*spec.[jt]s?(x)']
|
testMatch: ['<rootDir>/packages/**/__tests__/**/*spec.[jt]s?(x)'],
|
||||||
|
testPathIgnorePatterns: process.env.SKIP_E2E
|
||||||
|
? // ignore example tests on netlify builds since they don't contribute
|
||||||
|
// to coverage and can cause netlify builds to fail
|
||||||
|
['/node_modules/', '/examples/__tests__']
|
||||||
|
: ['/node_modules/']
|
||||||
}
|
}
|
||||||
|
43
package.json
43
package.json
@ -1,26 +1,29 @@
|
|||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "3.0.0-alpha.0",
|
"version": "3.0.0-alpha.11",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "node scripts/dev.js",
|
"dev": "node scripts/dev.js",
|
||||||
"build": "node scripts/build.js",
|
"build": "node scripts/build.js",
|
||||||
"size-runtime": "node scripts/build.js runtime-dom size-check -p -f global",
|
"size": "node scripts/build.js vue runtime-dom size-check -p -f global",
|
||||||
"size-compiler": "node scripts/build.js compiler-dom -p -f global",
|
|
||||||
"size": "yarn size-runtime && yarn size-compiler",
|
|
||||||
"lint": "prettier --write --parser typescript \"packages/**/*.ts?(x)\"",
|
"lint": "prettier --write --parser typescript \"packages/**/*.ts?(x)\"",
|
||||||
|
"ls-lint": "ls-lint",
|
||||||
"test": "node scripts/build.js vue -f global -d && jest",
|
"test": "node scripts/build.js vue -f global -d && jest",
|
||||||
"test-dts": "node scripts/build.js reactivity runtime-core runtime-dom -t -f esm && tsd",
|
"test-dts": "node scripts/build.js shared reactivity runtime-core runtime-dom -dt -f esm-bundler && tsd",
|
||||||
"release": "node scripts/release.js"
|
"release": "node scripts/release.js",
|
||||||
|
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s",
|
||||||
|
"dev-compiler": "npm-run-all --parallel \"dev template-explorer\" serve",
|
||||||
|
"serve": "serve",
|
||||||
|
"open": "open http://localhost:5000/packages/template-explorer/local.html"
|
||||||
},
|
},
|
||||||
"types": "test-dts/index.d.ts",
|
"types": "test-dts/index.d.ts",
|
||||||
"tsd": {
|
"tsd": {
|
||||||
"directory": "test-dts"
|
"directory": "test-dts"
|
||||||
},
|
},
|
||||||
"gitHooks": {
|
"gitHooks": {
|
||||||
"pre-commit": "lint-staged",
|
"pre-commit": "ls-lint && lint-staged",
|
||||||
"commit-msg": "node scripts/verifyCommit.js"
|
"commit-msg": "node scripts/verifyCommit.js"
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
@ -33,29 +36,39 @@
|
|||||||
"git add"
|
"git add"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@microsoft/api-extractor": "^7.3.9",
|
"@microsoft/api-extractor": "^7.3.9",
|
||||||
|
"@rollup/plugin-commonjs": "^11.0.2",
|
||||||
"@rollup/plugin-json": "^4.0.0",
|
"@rollup/plugin-json": "^4.0.0",
|
||||||
|
"@rollup/plugin-node-resolve": "^7.1.1",
|
||||||
"@rollup/plugin-replace": "^2.2.1",
|
"@rollup/plugin-replace": "^2.2.1",
|
||||||
"@types/jest": "^24.0.21",
|
"@types/jest": "^25.1.4",
|
||||||
"@types/puppeteer": "^2.0.0",
|
"@types/puppeteer": "^2.0.0",
|
||||||
"brotli": "^1.3.2",
|
"brotli": "^1.3.2",
|
||||||
"chalk": "^2.4.2",
|
"chalk": "^2.4.2",
|
||||||
|
"conventional-changelog-cli": "^2.0.31",
|
||||||
|
"csstype": "^2.6.8",
|
||||||
"enquirer": "^2.3.2",
|
"enquirer": "^2.3.2",
|
||||||
"execa": "^2.0.4",
|
"execa": "^2.0.4",
|
||||||
"fs-extra": "^8.1.0",
|
"fs-extra": "^8.1.0",
|
||||||
"jest": "^24.9.0",
|
"jest": "^25.2.3",
|
||||||
"lint-staged": "^9.2.3",
|
"lint-staged": "^9.2.3",
|
||||||
"minimist": "^1.2.0",
|
"minimist": "^1.2.0",
|
||||||
|
"npm-run-all": "^4.1.5",
|
||||||
"prettier": "~1.14.0",
|
"prettier": "~1.14.0",
|
||||||
"puppeteer": "^2.0.0",
|
"puppeteer": "^2.0.0",
|
||||||
"rollup": "^1.19.4",
|
"rollup": "^2.2.0",
|
||||||
"rollup-plugin-terser": "^5.1.1",
|
"rollup-plugin-terser": "^5.3.0",
|
||||||
"rollup-plugin-typescript2": "^0.24.0",
|
"rollup-plugin-typescript2": "^0.27.0",
|
||||||
"semver": "^6.3.0",
|
"semver": "^6.3.0",
|
||||||
"ts-jest": "^24.0.2",
|
"serve": "^11.3.0",
|
||||||
|
"ts-jest": "^25.2.1",
|
||||||
"tsd": "^0.11.0",
|
"tsd": "^0.11.0",
|
||||||
"typescript": "^3.7.0",
|
"typescript": "^3.8.3",
|
||||||
"yorkie": "^2.0.0"
|
"yorkie": "^2.0.0",
|
||||||
|
"@ls-lint/ls-lint": "^1.8.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
exports[`compiler: codegen ArrayExpression 1`] = `
|
exports[`compiler: codegen ArrayExpression 1`] = `
|
||||||
"
|
"
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
return [
|
return [
|
||||||
foo,
|
foo,
|
||||||
bar(baz)
|
bar(baz)
|
||||||
@ -14,22 +14,18 @@ return function render() {
|
|||||||
|
|
||||||
exports[`compiler: codegen CacheExpression 1`] = `
|
exports[`compiler: codegen CacheExpression 1`] = `
|
||||||
"
|
"
|
||||||
export default function render() {
|
export function render(_ctx, _cache) {
|
||||||
const _ctx = this
|
|
||||||
const _cache = _ctx.$cache
|
|
||||||
return _cache[1] || (_cache[1] = foo)
|
return _cache[1] || (_cache[1] = foo)
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`compiler: codegen CacheExpression w/ isVNode: true 1`] = `
|
exports[`compiler: codegen CacheExpression w/ isVNode: true 1`] = `
|
||||||
"
|
"
|
||||||
export default function render() {
|
export function render(_ctx, _cache) {
|
||||||
const _ctx = this
|
|
||||||
const _cache = _ctx.$cache
|
|
||||||
return _cache[1] || (
|
return _cache[1] || (
|
||||||
setBlockTracking(-1),
|
_setBlockTracking(-1),
|
||||||
_cache[1] = foo,
|
_cache[1] = foo,
|
||||||
setBlockTracking(1),
|
_setBlockTracking(1),
|
||||||
_cache[1]
|
_cache[1]
|
||||||
)
|
)
|
||||||
}"
|
}"
|
||||||
@ -37,8 +33,8 @@ export default function render() {
|
|||||||
|
|
||||||
exports[`compiler: codegen ConditionalExpression 1`] = `
|
exports[`compiler: codegen ConditionalExpression 1`] = `
|
||||||
"
|
"
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
return ok
|
return ok
|
||||||
? foo()
|
? foo()
|
||||||
: orNot
|
: orNot
|
||||||
@ -50,8 +46,8 @@ return function render() {
|
|||||||
|
|
||||||
exports[`compiler: codegen Element (callExpression + objectExpression + TemplateChildNode[]) 1`] = `
|
exports[`compiler: codegen Element (callExpression + objectExpression + TemplateChildNode[]) 1`] = `
|
||||||
"
|
"
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
return _createVNode(\\"div\\", {
|
return _createVNode(\\"div\\", {
|
||||||
id: \\"foo\\",
|
id: \\"foo\\",
|
||||||
[prop]: bar,
|
[prop]: bar,
|
||||||
@ -63,23 +59,16 @@ return function render() {
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`compiler: codegen SequenceExpression 1`] = `
|
exports[`compiler: codegen assets + temps 1`] = `
|
||||||
"
|
"
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
return (foo, bar(baz))
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`compiler: codegen assets 1`] = `
|
|
||||||
"
|
|
||||||
return function render() {
|
|
||||||
with (this) {
|
|
||||||
const _component_Foo = _resolveComponent(\\"Foo\\")
|
const _component_Foo = _resolveComponent(\\"Foo\\")
|
||||||
const _component_bar_baz = _resolveComponent(\\"bar-baz\\")
|
const _component_bar_baz = _resolveComponent(\\"bar-baz\\")
|
||||||
const _component_barbaz = _resolveComponent(\\"barbaz\\")
|
const _component_barbaz = _resolveComponent(\\"barbaz\\")
|
||||||
const _directive_my_dir = _resolveDirective(\\"my_dir\\")
|
const _directive_my_dir_0 = _resolveDirective(\\"my_dir_0\\")
|
||||||
|
const _directive_my_dir_1 = _resolveDirective(\\"my_dir_1\\")
|
||||||
|
let _temp0, _temp1, _temp2
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@ -88,8 +77,8 @@ return function render() {
|
|||||||
|
|
||||||
exports[`compiler: codegen comment 1`] = `
|
exports[`compiler: codegen comment 1`] = `
|
||||||
"
|
"
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
return _createCommentVNode(\\"foo\\")
|
return _createCommentVNode(\\"foo\\")
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@ -97,18 +86,18 @@ return function render() {
|
|||||||
|
|
||||||
exports[`compiler: codegen compound expression 1`] = `
|
exports[`compiler: codegen compound expression 1`] = `
|
||||||
"
|
"
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
return _ctx.foo + _toString(bar)
|
return _ctx.foo + _toDisplayString(bar) + nested
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`compiler: codegen forNode 1`] = `
|
exports[`compiler: codegen forNode 1`] = `
|
||||||
"
|
"
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
return (foo, bar)
|
return (_openBlock(true), _createBlock(_Fragment, null, _renderList(), 1))
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@ -116,8 +105,8 @@ return function render() {
|
|||||||
exports[`compiler: codegen function mode preamble 1`] = `
|
exports[`compiler: codegen function mode preamble 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { createVNode: _createVNode, resolveDirective: _resolveDirective } = _Vue
|
const { createVNode: _createVNode, resolveDirective: _resolveDirective } = _Vue
|
||||||
|
|
||||||
return null
|
return null
|
||||||
@ -126,10 +115,9 @@ return function render() {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`compiler: codegen function mode preamble w/ prefixIdentifiers: true 1`] = `
|
exports[`compiler: codegen function mode preamble w/ prefixIdentifiers: true 1`] = `
|
||||||
"const { createVNode, resolveDirective } = Vue
|
"const { createVNode: _createVNode, resolveDirective: _resolveDirective } = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
const _ctx = this
|
|
||||||
return null
|
return null
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@ -139,8 +127,8 @@ exports[`compiler: codegen hoists 1`] = `
|
|||||||
const _hoisted_1 = hello
|
const _hoisted_1 = hello
|
||||||
const _hoisted_2 = { id: \\"foo\\" }
|
const _hoisted_2 = { id: \\"foo\\" }
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@ -148,44 +136,59 @@ return function render() {
|
|||||||
|
|
||||||
exports[`compiler: codegen ifNode 1`] = `
|
exports[`compiler: codegen ifNode 1`] = `
|
||||||
"
|
"
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
return (foo, bar)
|
return foo
|
||||||
|
? bar
|
||||||
|
: baz
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`compiler: codegen interpolation 1`] = `
|
exports[`compiler: codegen interpolation 1`] = `
|
||||||
"
|
"
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
return _toString(hello)
|
return _toDisplayString(hello)
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`compiler: codegen module mode preamble 1`] = `
|
exports[`compiler: codegen module mode preamble 1`] = `
|
||||||
"import { createVNode, resolveDirective } from \\"vue\\"
|
"import { createVNode as _createVNode, resolveDirective as _resolveDirective } from \\"vue\\"
|
||||||
|
|
||||||
export default function render() {
|
export function render(_ctx, _cache) {
|
||||||
const _ctx = this
|
|
||||||
return null
|
return null
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`compiler: codegen prefixIdentifiers: true should inject _ctx statement 1`] = `
|
exports[`compiler: codegen module mode preamble w/ optimizeBindings: true 1`] = `
|
||||||
"
|
"import { createVNode, resolveDirective } from \\"vue\\"
|
||||||
return function render() {
|
|
||||||
const _ctx = this
|
// Binding optimization for webpack code-split
|
||||||
|
const _createVNode = createVNode, _resolveDirective = resolveDirective
|
||||||
|
|
||||||
|
export function render(_ctx, _cache) {
|
||||||
return null
|
return null
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`compiler: codegen static text 1`] = `
|
exports[`compiler: codegen static text 1`] = `
|
||||||
"
|
"
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
return \\"hello\\"
|
return \\"hello\\"
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`compiler: codegen temps 1`] = `
|
||||||
|
"
|
||||||
|
return function render(_ctx, _cache) {
|
||||||
|
with (_ctx) {
|
||||||
|
let _temp0, _temp1, _temp2
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
@ -3,21 +3,23 @@
|
|||||||
exports[`compiler: integration tests function mode 1`] = `
|
exports[`compiler: integration tests function mode 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { toString: _toString, openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, createCommentVNode: _createCommentVNode, Fragment: _Fragment, renderList: _renderList, createTextVNode: _createTextVNode } = _Vue
|
const { toDisplayString: _toDisplayString, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock, createCommentVNode: _createCommentVNode, createTextVNode: _createTextVNode, Fragment: _Fragment, renderList: _renderList } = _Vue
|
||||||
|
|
||||||
return (_openBlock(), _createBlock(\\"div\\", {
|
return (_openBlock(), _createBlock(\\"div\\", {
|
||||||
id: \\"foo\\",
|
id: \\"foo\\",
|
||||||
class: bar.baz
|
class: bar.baz
|
||||||
}, [
|
}, [
|
||||||
_createTextVNode(_toString(world.burn()) + \\" \\", 1 /* TEXT */),
|
_createTextVNode(_toDisplayString(world.burn()) + \\" \\", 1 /* TEXT */),
|
||||||
(_openBlock(), ok
|
ok
|
||||||
? _createBlock(\\"div\\", { key: 0 }, \\"yes\\")
|
? (_openBlock(), _createBlock(\\"div\\", { key: 0 }, \\"yes\\"))
|
||||||
: _createBlock(_Fragment, { key: 1 }, [\\"no\\"])),
|
: (_openBlock(), _createBlock(_Fragment, { key: 1 }, [
|
||||||
(_openBlock(false), _createBlock(_Fragment, null, _renderList(list, (value, index) => {
|
_createTextVNode(\\"no\\")
|
||||||
|
], 64 /* STABLE_FRAGMENT */)),
|
||||||
|
(_openBlock(true), _createBlock(_Fragment, null, _renderList(list, (value, index) => {
|
||||||
return (_openBlock(), _createBlock(\\"div\\", null, [
|
return (_openBlock(), _createBlock(\\"div\\", null, [
|
||||||
_createVNode(\\"span\\", null, _toString(value + index), 1 /* TEXT */)
|
_createVNode(\\"span\\", null, _toDisplayString(value + index), 1 /* TEXT */)
|
||||||
]))
|
]))
|
||||||
}), 256 /* UNKEYED_FRAGMENT */))
|
}), 256 /* UNKEYED_FRAGMENT */))
|
||||||
], 2 /* CLASS */))
|
], 2 /* CLASS */))
|
||||||
@ -26,21 +28,22 @@ return function render() {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`compiler: integration tests function mode w/ prefixIdentifiers: true 1`] = `
|
exports[`compiler: integration tests function mode w/ prefixIdentifiers: true 1`] = `
|
||||||
"const { toString, openBlock, createVNode, createBlock, createCommentVNode, Fragment, renderList, createTextVNode } = Vue
|
"const { toDisplayString: _toDisplayString, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock, createCommentVNode: _createCommentVNode, createTextVNode: _createTextVNode, Fragment: _Fragment, renderList: _renderList } = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
const _ctx = this
|
return (_openBlock(), _createBlock(\\"div\\", {
|
||||||
return (openBlock(), createBlock(\\"div\\", {
|
|
||||||
id: \\"foo\\",
|
id: \\"foo\\",
|
||||||
class: _ctx.bar.baz
|
class: _ctx.bar.baz
|
||||||
}, [
|
}, [
|
||||||
createTextVNode(toString(_ctx.world.burn()) + \\" \\", 1 /* TEXT */),
|
_createTextVNode(_toDisplayString(_ctx.world.burn()) + \\" \\", 1 /* TEXT */),
|
||||||
(openBlock(), (_ctx.ok)
|
(_ctx.ok)
|
||||||
? createBlock(\\"div\\", { key: 0 }, \\"yes\\")
|
? (_openBlock(), _createBlock(\\"div\\", { key: 0 }, \\"yes\\"))
|
||||||
: createBlock(Fragment, { key: 1 }, [\\"no\\"])),
|
: (_openBlock(), _createBlock(_Fragment, { key: 1 }, [
|
||||||
(openBlock(false), createBlock(Fragment, null, renderList(_ctx.list, (value, index) => {
|
_createTextVNode(\\"no\\")
|
||||||
return (openBlock(), createBlock(\\"div\\", null, [
|
], 64 /* STABLE_FRAGMENT */)),
|
||||||
createVNode(\\"span\\", null, toString(value + index), 1 /* TEXT */)
|
(_openBlock(true), _createBlock(_Fragment, null, _renderList(_ctx.list, (value, index) => {
|
||||||
|
return (_openBlock(), _createBlock(\\"div\\", null, [
|
||||||
|
_createVNode(\\"span\\", null, _toDisplayString(value + index), 1 /* TEXT */)
|
||||||
]))
|
]))
|
||||||
}), 256 /* UNKEYED_FRAGMENT */))
|
}), 256 /* UNKEYED_FRAGMENT */))
|
||||||
], 2 /* CLASS */))
|
], 2 /* CLASS */))
|
||||||
@ -48,21 +51,22 @@ return function render() {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`compiler: integration tests module mode 1`] = `
|
exports[`compiler: integration tests module mode 1`] = `
|
||||||
"import { toString, openBlock, createVNode, createBlock, createCommentVNode, Fragment, renderList, createTextVNode } from \\"vue\\"
|
"import { toDisplayString as _toDisplayString, createVNode as _createVNode, openBlock as _openBlock, createBlock as _createBlock, createCommentVNode as _createCommentVNode, createTextVNode as _createTextVNode, Fragment as _Fragment, renderList as _renderList } from \\"vue\\"
|
||||||
|
|
||||||
export default function render() {
|
export function render(_ctx, _cache) {
|
||||||
const _ctx = this
|
return (_openBlock(), _createBlock(\\"div\\", {
|
||||||
return (openBlock(), createBlock(\\"div\\", {
|
|
||||||
id: \\"foo\\",
|
id: \\"foo\\",
|
||||||
class: _ctx.bar.baz
|
class: _ctx.bar.baz
|
||||||
}, [
|
}, [
|
||||||
createTextVNode(toString(_ctx.world.burn()) + \\" \\", 1 /* TEXT */),
|
_createTextVNode(_toDisplayString(_ctx.world.burn()) + \\" \\", 1 /* TEXT */),
|
||||||
(openBlock(), (_ctx.ok)
|
(_ctx.ok)
|
||||||
? createBlock(\\"div\\", { key: 0 }, \\"yes\\")
|
? (_openBlock(), _createBlock(\\"div\\", { key: 0 }, \\"yes\\"))
|
||||||
: createBlock(Fragment, { key: 1 }, [\\"no\\"])),
|
: (_openBlock(), _createBlock(_Fragment, { key: 1 }, [
|
||||||
(openBlock(false), createBlock(Fragment, null, renderList(_ctx.list, (value, index) => {
|
_createTextVNode(\\"no\\")
|
||||||
return (openBlock(), createBlock(\\"div\\", null, [
|
], 64 /* STABLE_FRAGMENT */)),
|
||||||
createVNode(\\"span\\", null, toString(value + index), 1 /* TEXT */)
|
(_openBlock(true), _createBlock(_Fragment, null, _renderList(_ctx.list, (value, index) => {
|
||||||
|
return (_openBlock(), _createBlock(\\"div\\", null, [
|
||||||
|
_createVNode(\\"span\\", null, _toDisplayString(value + index), 1 /* TEXT */)
|
||||||
]))
|
]))
|
||||||
}), 256 /* UNKEYED_FRAGMENT */))
|
}), 256 /* UNKEYED_FRAGMENT */))
|
||||||
], 2 /* CLASS */))
|
], 2 /* CLASS */))
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,91 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`scopeId compiler support should push scopeId for hoisted nodes 1`] = `
|
||||||
|
"import { createVNode as _createVNode, toDisplayString as _toDisplayString, createTextVNode as _createTextVNode, openBlock as _openBlock, createBlock as _createBlock, withScopeId as _withScopeId, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \\"vue\\"
|
||||||
|
const _withId = _withScopeId(\\"test\\")
|
||||||
|
|
||||||
|
_pushScopeId(\\"test\\")
|
||||||
|
const _hoisted_1 = _createVNode(\\"div\\", null, \\"hello\\", -1 /* HOISTED */)
|
||||||
|
const _hoisted_2 = _createVNode(\\"div\\", null, \\"world\\", -1 /* HOISTED */)
|
||||||
|
_popScopeId()
|
||||||
|
|
||||||
|
export const render = _withId(function render(_ctx, _cache) {
|
||||||
|
return (_openBlock(), _createBlock(\\"div\\", null, [
|
||||||
|
_hoisted_1,
|
||||||
|
_createTextVNode(_toDisplayString(_ctx.foo), 1 /* TEXT */),
|
||||||
|
_hoisted_2
|
||||||
|
]))
|
||||||
|
})"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`scopeId compiler support should wrap default slot 1`] = `
|
||||||
|
"import { createVNode as _createVNode, resolveComponent as _resolveComponent, withCtx as _withCtx, openBlock as _openBlock, createBlock as _createBlock, withScopeId as _withScopeId } from \\"vue\\"
|
||||||
|
const _withId = _withScopeId(\\"test\\")
|
||||||
|
|
||||||
|
export const render = _withId(function render(_ctx, _cache) {
|
||||||
|
const _component_Child = _resolveComponent(\\"Child\\")
|
||||||
|
|
||||||
|
return (_openBlock(), _createBlock(_component_Child, null, {
|
||||||
|
default: _withId(() => [
|
||||||
|
_createVNode(\\"div\\")
|
||||||
|
]),
|
||||||
|
_: 1
|
||||||
|
}))
|
||||||
|
})"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`scopeId compiler support should wrap dynamic slots 1`] = `
|
||||||
|
"import { createVNode as _createVNode, resolveComponent as _resolveComponent, withCtx as _withCtx, renderList as _renderList, createSlots as _createSlots, openBlock as _openBlock, createBlock as _createBlock, withScopeId as _withScopeId } from \\"vue\\"
|
||||||
|
const _withId = _withScopeId(\\"test\\")
|
||||||
|
|
||||||
|
export const render = _withId(function render(_ctx, _cache) {
|
||||||
|
const _component_Child = _resolveComponent(\\"Child\\")
|
||||||
|
|
||||||
|
return (_openBlock(), _createBlock(_component_Child, null, _createSlots({ _: 1 }, [
|
||||||
|
(_ctx.ok)
|
||||||
|
? {
|
||||||
|
name: \\"foo\\",
|
||||||
|
fn: _withId(() => [
|
||||||
|
_createVNode(\\"div\\")
|
||||||
|
])
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
_renderList(_ctx.list, (i) => {
|
||||||
|
return {
|
||||||
|
name: i,
|
||||||
|
fn: _withId(() => [
|
||||||
|
_createVNode(\\"div\\")
|
||||||
|
])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]), 1024 /* DYNAMIC_SLOTS */))
|
||||||
|
})"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`scopeId compiler support should wrap named slots 1`] = `
|
||||||
|
"import { toDisplayString as _toDisplayString, createTextVNode as _createTextVNode, createVNode as _createVNode, resolveComponent as _resolveComponent, withCtx as _withCtx, openBlock as _openBlock, createBlock as _createBlock, withScopeId as _withScopeId } from \\"vue\\"
|
||||||
|
const _withId = _withScopeId(\\"test\\")
|
||||||
|
|
||||||
|
export const render = _withId(function render(_ctx, _cache) {
|
||||||
|
const _component_Child = _resolveComponent(\\"Child\\")
|
||||||
|
|
||||||
|
return (_openBlock(), _createBlock(_component_Child, null, {
|
||||||
|
foo: _withId(({ msg }) => [
|
||||||
|
_createTextVNode(_toDisplayString(msg), 1 /* TEXT */)
|
||||||
|
]),
|
||||||
|
bar: _withId(() => [
|
||||||
|
_createVNode(\\"div\\")
|
||||||
|
]),
|
||||||
|
_: 1
|
||||||
|
}))
|
||||||
|
})"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`scopeId compiler support should wrap render function 1`] = `
|
||||||
|
"import { createVNode as _createVNode, openBlock as _openBlock, createBlock as _createBlock, withScopeId as _withScopeId } from \\"vue\\"
|
||||||
|
const _withId = _withScopeId(\\"test\\")
|
||||||
|
|
||||||
|
export const render = _withId(function render(_ctx, _cache) {
|
||||||
|
return (_openBlock(), _createBlock(\\"div\\"))
|
||||||
|
})"
|
||||||
|
`;
|
@ -9,20 +9,28 @@ import {
|
|||||||
createArrayExpression,
|
createArrayExpression,
|
||||||
createCompoundExpression,
|
createCompoundExpression,
|
||||||
createInterpolation,
|
createInterpolation,
|
||||||
createSequenceExpression,
|
|
||||||
createCallExpression,
|
createCallExpression,
|
||||||
createConditionalExpression,
|
createConditionalExpression,
|
||||||
IfCodegenNode,
|
|
||||||
ForCodegenNode,
|
ForCodegenNode,
|
||||||
createCacheExpression
|
createCacheExpression,
|
||||||
|
createTemplateLiteral,
|
||||||
|
createBlockStatement,
|
||||||
|
createIfStatement,
|
||||||
|
createAssignmentExpression,
|
||||||
|
IfConditionalExpression,
|
||||||
|
createVNodeCall,
|
||||||
|
VNodeCall,
|
||||||
|
DirectiveArguments
|
||||||
} from '../src'
|
} from '../src'
|
||||||
import {
|
import {
|
||||||
CREATE_VNODE,
|
CREATE_VNODE,
|
||||||
TO_STRING,
|
TO_DISPLAY_STRING,
|
||||||
RESOLVE_DIRECTIVE,
|
RESOLVE_DIRECTIVE,
|
||||||
helperNameMap,
|
helperNameMap,
|
||||||
RESOLVE_COMPONENT,
|
RESOLVE_COMPONENT,
|
||||||
CREATE_COMMENT
|
CREATE_COMMENT,
|
||||||
|
FRAGMENT,
|
||||||
|
RENDER_LIST
|
||||||
} from '../src/runtimeHelpers'
|
} from '../src/runtimeHelpers'
|
||||||
import { createElementWithCodegen } from './testUtils'
|
import { createElementWithCodegen } from './testUtils'
|
||||||
import { PatchFlags } from '@vue/shared'
|
import { PatchFlags } from '@vue/shared'
|
||||||
@ -37,6 +45,7 @@ function createRoot(options: Partial<RootNode> = {}): RootNode {
|
|||||||
imports: [],
|
imports: [],
|
||||||
hoists: [],
|
hoists: [],
|
||||||
cached: 0,
|
cached: 0,
|
||||||
|
temps: 0,
|
||||||
codegenNode: createSimpleExpression(`null`, false),
|
codegenNode: createSimpleExpression(`null`, false),
|
||||||
loc: locStub,
|
loc: locStub,
|
||||||
...options
|
...options
|
||||||
@ -49,11 +58,33 @@ describe('compiler: codegen', () => {
|
|||||||
helpers: [CREATE_VNODE, RESOLVE_DIRECTIVE]
|
helpers: [CREATE_VNODE, RESOLVE_DIRECTIVE]
|
||||||
})
|
})
|
||||||
const { code } = generate(root, { mode: 'module' })
|
const { code } = generate(root, { mode: 'module' })
|
||||||
|
expect(code).toMatch(
|
||||||
|
`import { ${helperNameMap[CREATE_VNODE]} as _${
|
||||||
|
helperNameMap[CREATE_VNODE]
|
||||||
|
}, ${helperNameMap[RESOLVE_DIRECTIVE]} as _${
|
||||||
|
helperNameMap[RESOLVE_DIRECTIVE]
|
||||||
|
} } from "vue"`
|
||||||
|
)
|
||||||
|
expect(code).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('module mode preamble w/ optimizeBindings: true', () => {
|
||||||
|
const root = createRoot({
|
||||||
|
helpers: [CREATE_VNODE, RESOLVE_DIRECTIVE]
|
||||||
|
})
|
||||||
|
const { code } = generate(root, { mode: 'module', optimizeBindings: true })
|
||||||
expect(code).toMatch(
|
expect(code).toMatch(
|
||||||
`import { ${helperNameMap[CREATE_VNODE]}, ${
|
`import { ${helperNameMap[CREATE_VNODE]}, ${
|
||||||
helperNameMap[RESOLVE_DIRECTIVE]
|
helperNameMap[RESOLVE_DIRECTIVE]
|
||||||
} } from "vue"`
|
} } from "vue"`
|
||||||
)
|
)
|
||||||
|
expect(code).toMatch(
|
||||||
|
`const _${helperNameMap[CREATE_VNODE]} = ${
|
||||||
|
helperNameMap[CREATE_VNODE]
|
||||||
|
}, _${helperNameMap[RESOLVE_DIRECTIVE]} = ${
|
||||||
|
helperNameMap[RESOLVE_DIRECTIVE]
|
||||||
|
}`
|
||||||
|
)
|
||||||
expect(code).toMatchSnapshot()
|
expect(code).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -83,17 +114,20 @@ describe('compiler: codegen', () => {
|
|||||||
})
|
})
|
||||||
expect(code).not.toMatch(`const _Vue = Vue`)
|
expect(code).not.toMatch(`const _Vue = Vue`)
|
||||||
expect(code).toMatch(
|
expect(code).toMatch(
|
||||||
`const { ${helperNameMap[CREATE_VNODE]}, ${
|
`const { ${helperNameMap[CREATE_VNODE]}: _${
|
||||||
|
helperNameMap[CREATE_VNODE]
|
||||||
|
}, ${helperNameMap[RESOLVE_DIRECTIVE]}: _${
|
||||||
helperNameMap[RESOLVE_DIRECTIVE]
|
helperNameMap[RESOLVE_DIRECTIVE]
|
||||||
} } = Vue`
|
} } = Vue`
|
||||||
)
|
)
|
||||||
expect(code).toMatchSnapshot()
|
expect(code).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('assets', () => {
|
test('assets + temps', () => {
|
||||||
const root = createRoot({
|
const root = createRoot({
|
||||||
components: [`Foo`, `bar-baz`, `barbaz`],
|
components: [`Foo`, `bar-baz`, `barbaz`],
|
||||||
directives: [`my_dir`]
|
directives: [`my_dir_0`, `my_dir_1`],
|
||||||
|
temps: 3
|
||||||
})
|
})
|
||||||
const { code } = generate(root, { mode: 'function' })
|
const { code } = generate(root, { mode: 'function' })
|
||||||
expect(code).toMatch(
|
expect(code).toMatch(
|
||||||
@ -110,10 +144,16 @@ describe('compiler: codegen', () => {
|
|||||||
}("barbaz")\n`
|
}("barbaz")\n`
|
||||||
)
|
)
|
||||||
expect(code).toMatch(
|
expect(code).toMatch(
|
||||||
`const _directive_my_dir = _${
|
`const _directive_my_dir_0 = _${
|
||||||
helperNameMap[RESOLVE_DIRECTIVE]
|
helperNameMap[RESOLVE_DIRECTIVE]
|
||||||
}("my_dir")\n`
|
}("my_dir_0")\n`
|
||||||
)
|
)
|
||||||
|
expect(code).toMatch(
|
||||||
|
`const _directive_my_dir_1 = _${
|
||||||
|
helperNameMap[RESOLVE_DIRECTIVE]
|
||||||
|
}("my_dir_1")\n`
|
||||||
|
)
|
||||||
|
expect(code).toMatch(`let _temp0, _temp1, _temp2`)
|
||||||
expect(code).toMatchSnapshot()
|
expect(code).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -138,9 +178,12 @@ describe('compiler: codegen', () => {
|
|||||||
expect(code).toMatchSnapshot()
|
expect(code).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('prefixIdentifiers: true should inject _ctx statement', () => {
|
test('temps', () => {
|
||||||
const { code } = generate(createRoot(), { prefixIdentifiers: true })
|
const root = createRoot({
|
||||||
expect(code).toMatch(`const _ctx = this\n`)
|
temps: 3
|
||||||
|
})
|
||||||
|
const { code } = generate(root)
|
||||||
|
expect(code).toMatch(`let _temp0, _temp1, _temp2`)
|
||||||
expect(code).toMatchSnapshot()
|
expect(code).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -164,7 +207,7 @@ describe('compiler: codegen', () => {
|
|||||||
codegenNode: createInterpolation(`hello`, locStub)
|
codegenNode: createInterpolation(`hello`, locStub)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
expect(code).toMatch(`return _${helperNameMap[TO_STRING]}(hello)`)
|
expect(code).toMatch(`return _${helperNameMap[TO_DISPLAY_STRING]}(hello)`)
|
||||||
expect(code).toMatchSnapshot()
|
expect(code).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -193,11 +236,15 @@ describe('compiler: codegen', () => {
|
|||||||
type: NodeTypes.INTERPOLATION,
|
type: NodeTypes.INTERPOLATION,
|
||||||
loc: locStub,
|
loc: locStub,
|
||||||
content: createSimpleExpression(`bar`, false, locStub)
|
content: createSimpleExpression(`bar`, false, locStub)
|
||||||
}
|
},
|
||||||
|
// nested compound
|
||||||
|
createCompoundExpression([` + `, `nested`])
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
expect(code).toMatch(`return _ctx.foo + _${helperNameMap[TO_STRING]}(bar)`)
|
expect(code).toMatch(
|
||||||
|
`return _ctx.foo + _${helperNameMap[TO_DISPLAY_STRING]}(bar) + nested`
|
||||||
|
)
|
||||||
expect(code).toMatchSnapshot()
|
expect(code).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -208,14 +255,15 @@ describe('compiler: codegen', () => {
|
|||||||
type: NodeTypes.IF,
|
type: NodeTypes.IF,
|
||||||
loc: locStub,
|
loc: locStub,
|
||||||
branches: [],
|
branches: [],
|
||||||
codegenNode: createSequenceExpression([
|
codegenNode: createConditionalExpression(
|
||||||
createSimpleExpression('foo', false),
|
createSimpleExpression('foo', false),
|
||||||
createSimpleExpression('bar', false)
|
createSimpleExpression('bar', false),
|
||||||
]) as IfCodegenNode
|
createSimpleExpression('baz', false)
|
||||||
|
) as IfConditionalExpression
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
expect(code).toMatch(`return (foo, bar)`)
|
expect(code).toMatch(/return foo\s+\? bar\s+: baz/)
|
||||||
expect(code).toMatchSnapshot()
|
expect(code).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -230,21 +278,30 @@ describe('compiler: codegen', () => {
|
|||||||
keyAlias: undefined,
|
keyAlias: undefined,
|
||||||
objectIndexAlias: undefined,
|
objectIndexAlias: undefined,
|
||||||
children: [],
|
children: [],
|
||||||
codegenNode: createSequenceExpression([
|
parseResult: {} as any,
|
||||||
createSimpleExpression('foo', false),
|
codegenNode: {
|
||||||
createSimpleExpression('bar', false)
|
type: NodeTypes.VNODE_CALL,
|
||||||
]) as ForCodegenNode
|
tag: FRAGMENT,
|
||||||
|
isBlock: true,
|
||||||
|
isForBlock: true,
|
||||||
|
props: undefined,
|
||||||
|
children: createCallExpression(RENDER_LIST),
|
||||||
|
patchFlag: '1',
|
||||||
|
dynamicProps: undefined,
|
||||||
|
directives: undefined,
|
||||||
|
loc: locStub
|
||||||
|
} as ForCodegenNode
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
expect(code).toMatch(`return (foo, bar)`)
|
expect(code).toMatch(`openBlock(true)`)
|
||||||
expect(code).toMatchSnapshot()
|
expect(code).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Element (callExpression + objectExpression + TemplateChildNode[])', () => {
|
test('Element (callExpression + objectExpression + TemplateChildNode[])', () => {
|
||||||
const { code } = generate(
|
const { code } = generate(
|
||||||
createRoot({
|
createRoot({
|
||||||
codegenNode: createElementWithCodegen([
|
codegenNode: createElementWithCodegen(
|
||||||
// string
|
// string
|
||||||
`"div"`,
|
`"div"`,
|
||||||
// ObjectExpression
|
// ObjectExpression
|
||||||
@ -275,7 +332,7 @@ describe('compiler: codegen', () => {
|
|||||||
),
|
),
|
||||||
// ChildNode[]
|
// ChildNode[]
|
||||||
[
|
[
|
||||||
createElementWithCodegen([
|
createElementWithCodegen(
|
||||||
`"p"`,
|
`"p"`,
|
||||||
createObjectExpression(
|
createObjectExpression(
|
||||||
[
|
[
|
||||||
@ -287,11 +344,11 @@ describe('compiler: codegen', () => {
|
|||||||
],
|
],
|
||||||
locStub
|
locStub
|
||||||
)
|
)
|
||||||
])
|
)
|
||||||
],
|
],
|
||||||
// flag
|
// flag
|
||||||
PatchFlags.FULL_PROPS + ''
|
PatchFlags.FULL_PROPS + ''
|
||||||
])
|
)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
expect(code).toMatch(`
|
expect(code).toMatch(`
|
||||||
@ -321,19 +378,6 @@ describe('compiler: codegen', () => {
|
|||||||
expect(code).toMatchSnapshot()
|
expect(code).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('SequenceExpression', () => {
|
|
||||||
const { code } = generate(
|
|
||||||
createRoot({
|
|
||||||
codegenNode: createSequenceExpression([
|
|
||||||
createSimpleExpression(`foo`, false),
|
|
||||||
createCallExpression(`bar`, [`baz`])
|
|
||||||
])
|
|
||||||
})
|
|
||||||
)
|
|
||||||
expect(code).toMatch(`return (foo, bar(baz))`)
|
|
||||||
expect(code).toMatchSnapshot()
|
|
||||||
})
|
|
||||||
|
|
||||||
test('ConditionalExpression', () => {
|
test('ConditionalExpression', () => {
|
||||||
const { code } = generate(
|
const { code } = generate(
|
||||||
createRoot({
|
createRoot({
|
||||||
@ -372,7 +416,6 @@ describe('compiler: codegen', () => {
|
|||||||
prefixIdentifiers: true
|
prefixIdentifiers: true
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
expect(code).toMatch(`const _cache = _ctx.$cache`)
|
|
||||||
expect(code).toMatch(`_cache[1] || (_cache[1] = foo)`)
|
expect(code).toMatch(`_cache[1] || (_cache[1] = foo)`)
|
||||||
expect(code).toMatchSnapshot()
|
expect(code).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
@ -392,17 +435,309 @@ describe('compiler: codegen', () => {
|
|||||||
prefixIdentifiers: true
|
prefixIdentifiers: true
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
expect(code).toMatch(`const _cache = _ctx.$cache`)
|
|
||||||
expect(code).toMatch(
|
expect(code).toMatch(
|
||||||
`
|
`
|
||||||
_cache[1] || (
|
_cache[1] || (
|
||||||
setBlockTracking(-1),
|
_setBlockTracking(-1),
|
||||||
_cache[1] = foo,
|
_cache[1] = foo,
|
||||||
setBlockTracking(1),
|
_setBlockTracking(1),
|
||||||
_cache[1]
|
_cache[1]
|
||||||
)
|
)
|
||||||
`.trim()
|
`.trim()
|
||||||
)
|
)
|
||||||
expect(code).toMatchSnapshot()
|
expect(code).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('TemplateLiteral', () => {
|
||||||
|
const { code } = generate(
|
||||||
|
createRoot({
|
||||||
|
codegenNode: createCallExpression(`_push`, [
|
||||||
|
createTemplateLiteral([
|
||||||
|
`foo`,
|
||||||
|
createCallExpression(`_renderAttr`, ['id', 'foo']),
|
||||||
|
`bar`
|
||||||
|
])
|
||||||
|
])
|
||||||
|
}),
|
||||||
|
{ ssr: true, mode: 'module' }
|
||||||
|
)
|
||||||
|
expect(code).toMatchInlineSnapshot(`
|
||||||
|
"
|
||||||
|
export function ssrRender(_ctx, _push, _parent) {
|
||||||
|
_push(\`foo\${_renderAttr(id, foo)}bar\`)
|
||||||
|
}"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('IfStatement', () => {
|
||||||
|
test('if', () => {
|
||||||
|
const { code } = generate(
|
||||||
|
createRoot({
|
||||||
|
codegenNode: createBlockStatement([
|
||||||
|
createIfStatement(
|
||||||
|
createSimpleExpression('foo', false),
|
||||||
|
createBlockStatement([createCallExpression(`ok`)])
|
||||||
|
)
|
||||||
|
])
|
||||||
|
}),
|
||||||
|
{ ssr: true, mode: 'module' }
|
||||||
|
)
|
||||||
|
expect(code).toMatchInlineSnapshot(`
|
||||||
|
"
|
||||||
|
export function ssrRender(_ctx, _push, _parent) {
|
||||||
|
if (foo) {
|
||||||
|
ok()
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('if/else', () => {
|
||||||
|
const { code } = generate(
|
||||||
|
createRoot({
|
||||||
|
codegenNode: createBlockStatement([
|
||||||
|
createIfStatement(
|
||||||
|
createSimpleExpression('foo', false),
|
||||||
|
createBlockStatement([createCallExpression(`foo`)]),
|
||||||
|
createBlockStatement([createCallExpression('bar')])
|
||||||
|
)
|
||||||
|
])
|
||||||
|
}),
|
||||||
|
{ ssr: true, mode: 'module' }
|
||||||
|
)
|
||||||
|
expect(code).toMatchInlineSnapshot(`
|
||||||
|
"
|
||||||
|
export function ssrRender(_ctx, _push, _parent) {
|
||||||
|
if (foo) {
|
||||||
|
foo()
|
||||||
|
} else {
|
||||||
|
bar()
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('if/else-if', () => {
|
||||||
|
const { code } = generate(
|
||||||
|
createRoot({
|
||||||
|
codegenNode: createBlockStatement([
|
||||||
|
createIfStatement(
|
||||||
|
createSimpleExpression('foo', false),
|
||||||
|
createBlockStatement([createCallExpression(`foo`)]),
|
||||||
|
createIfStatement(
|
||||||
|
createSimpleExpression('bar', false),
|
||||||
|
createBlockStatement([createCallExpression(`bar`)])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
])
|
||||||
|
}),
|
||||||
|
{ ssr: true, mode: 'module' }
|
||||||
|
)
|
||||||
|
expect(code).toMatchInlineSnapshot(`
|
||||||
|
"
|
||||||
|
export function ssrRender(_ctx, _push, _parent) {
|
||||||
|
if (foo) {
|
||||||
|
foo()
|
||||||
|
} else if (bar) {
|
||||||
|
bar()
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('if/else-if/else', () => {
|
||||||
|
const { code } = generate(
|
||||||
|
createRoot({
|
||||||
|
codegenNode: createBlockStatement([
|
||||||
|
createIfStatement(
|
||||||
|
createSimpleExpression('foo', false),
|
||||||
|
createBlockStatement([createCallExpression(`foo`)]),
|
||||||
|
createIfStatement(
|
||||||
|
createSimpleExpression('bar', false),
|
||||||
|
createBlockStatement([createCallExpression(`bar`)]),
|
||||||
|
createBlockStatement([createCallExpression('baz')])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
])
|
||||||
|
}),
|
||||||
|
{ ssr: true, mode: 'module' }
|
||||||
|
)
|
||||||
|
expect(code).toMatchInlineSnapshot(`
|
||||||
|
"
|
||||||
|
export function ssrRender(_ctx, _push, _parent) {
|
||||||
|
if (foo) {
|
||||||
|
foo()
|
||||||
|
} else if (bar) {
|
||||||
|
bar()
|
||||||
|
} else {
|
||||||
|
baz()
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('AssignmentExpression', () => {
|
||||||
|
const { code } = generate(
|
||||||
|
createRoot({
|
||||||
|
codegenNode: createAssignmentExpression(
|
||||||
|
createSimpleExpression(`foo`, false),
|
||||||
|
createSimpleExpression(`bar`, false)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
expect(code).toMatchInlineSnapshot(`
|
||||||
|
"
|
||||||
|
return function render(_ctx, _cache) {
|
||||||
|
with (_ctx) {
|
||||||
|
return foo = bar
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('VNodeCall', () => {
|
||||||
|
function genCode(node: VNodeCall) {
|
||||||
|
return generate(
|
||||||
|
createRoot({
|
||||||
|
codegenNode: node
|
||||||
|
})
|
||||||
|
).code.match(/with \(_ctx\) \{\s+([^]+)\s+\}\s+\}$/)![1]
|
||||||
|
}
|
||||||
|
|
||||||
|
const mockProps = createObjectExpression([
|
||||||
|
createObjectProperty(`foo`, createSimpleExpression(`bar`, true))
|
||||||
|
])
|
||||||
|
const mockChildren = createCompoundExpression(['children'])
|
||||||
|
const mockDirs = createArrayExpression([
|
||||||
|
createArrayExpression([`foo`, createSimpleExpression(`bar`, false)])
|
||||||
|
]) as DirectiveArguments
|
||||||
|
|
||||||
|
test('tag only', () => {
|
||||||
|
expect(genCode(createVNodeCall(null, `"div"`))).toMatchInlineSnapshot(`
|
||||||
|
"return _createVNode(\\"div\\")
|
||||||
|
"
|
||||||
|
`)
|
||||||
|
expect(genCode(createVNodeCall(null, FRAGMENT))).toMatchInlineSnapshot(`
|
||||||
|
"return _createVNode(_Fragment)
|
||||||
|
"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('with props', () => {
|
||||||
|
expect(genCode(createVNodeCall(null, `"div"`, mockProps)))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"return _createVNode(\\"div\\", { foo: \\"bar\\" })
|
||||||
|
"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('with children, no props', () => {
|
||||||
|
expect(genCode(createVNodeCall(null, `"div"`, undefined, mockChildren)))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"return _createVNode(\\"div\\", null, children)
|
||||||
|
"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('with children + props', () => {
|
||||||
|
expect(genCode(createVNodeCall(null, `"div"`, mockProps, mockChildren)))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"return _createVNode(\\"div\\", { foo: \\"bar\\" }, children)
|
||||||
|
"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('with patchFlag and no children/props', () => {
|
||||||
|
expect(genCode(createVNodeCall(null, `"div"`, undefined, undefined, '1')))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"return _createVNode(\\"div\\", null, null, 1)
|
||||||
|
"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('as block', () => {
|
||||||
|
expect(
|
||||||
|
genCode(
|
||||||
|
createVNodeCall(
|
||||||
|
null,
|
||||||
|
`"div"`,
|
||||||
|
mockProps,
|
||||||
|
mockChildren,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).toMatchInlineSnapshot(`
|
||||||
|
"return (_openBlock(), _createBlock(\\"div\\", { foo: \\"bar\\" }, children))
|
||||||
|
"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('as for block', () => {
|
||||||
|
expect(
|
||||||
|
genCode(
|
||||||
|
createVNodeCall(
|
||||||
|
null,
|
||||||
|
`"div"`,
|
||||||
|
mockProps,
|
||||||
|
mockChildren,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
true,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).toMatchInlineSnapshot(`
|
||||||
|
"return (_openBlock(true), _createBlock(\\"div\\", { foo: \\"bar\\" }, children))
|
||||||
|
"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('with directives', () => {
|
||||||
|
expect(
|
||||||
|
genCode(
|
||||||
|
createVNodeCall(
|
||||||
|
null,
|
||||||
|
`"div"`,
|
||||||
|
mockProps,
|
||||||
|
mockChildren,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
mockDirs
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).toMatchInlineSnapshot(`
|
||||||
|
"return _withDirectives(_createVNode(\\"div\\", { foo: \\"bar\\" }, children), [
|
||||||
|
[foo, bar]
|
||||||
|
])
|
||||||
|
"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('block + directives', () => {
|
||||||
|
expect(
|
||||||
|
genCode(
|
||||||
|
createVNodeCall(
|
||||||
|
null,
|
||||||
|
`"div"`,
|
||||||
|
mockProps,
|
||||||
|
mockChildren,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
mockDirs,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).toMatchInlineSnapshot(`
|
||||||
|
"return _withDirectives((_openBlock(), _createBlock(\\"div\\", { foo: \\"bar\\" }, children)), [
|
||||||
|
[foo, bar]
|
||||||
|
])
|
||||||
|
"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -44,7 +44,7 @@ describe('compiler: integration tests', () => {
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
test('function mode', async () => {
|
test('function mode', () => {
|
||||||
const { code, map } = compile(source, {
|
const { code, map } = compile(source, {
|
||||||
sourceMap: true,
|
sourceMap: true,
|
||||||
filename: `foo.vue`
|
filename: `foo.vue`
|
||||||
@ -54,7 +54,7 @@ describe('compiler: integration tests', () => {
|
|||||||
expect(map!.sources).toEqual([`foo.vue`])
|
expect(map!.sources).toEqual([`foo.vue`])
|
||||||
expect(map!.sourcesContent).toEqual([source])
|
expect(map!.sourcesContent).toEqual([source])
|
||||||
|
|
||||||
const consumer = await new SourceMapConsumer(map as RawSourceMap)
|
const consumer = new SourceMapConsumer(map as RawSourceMap)
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
consumer.originalPositionFor(getPositionInCode(code, `id`))
|
consumer.originalPositionFor(getPositionInCode(code, `id`))
|
||||||
@ -109,7 +109,7 @@ describe('compiler: integration tests', () => {
|
|||||||
).toMatchObject(getPositionInCode(source, `value + index`))
|
).toMatchObject(getPositionInCode(source, `value + index`))
|
||||||
})
|
})
|
||||||
|
|
||||||
test('function mode w/ prefixIdentifiers: true', async () => {
|
test('function mode w/ prefixIdentifiers: true', () => {
|
||||||
const { code, map } = compile(source, {
|
const { code, map } = compile(source, {
|
||||||
sourceMap: true,
|
sourceMap: true,
|
||||||
filename: `foo.vue`,
|
filename: `foo.vue`,
|
||||||
@ -120,7 +120,7 @@ describe('compiler: integration tests', () => {
|
|||||||
expect(map!.sources).toEqual([`foo.vue`])
|
expect(map!.sources).toEqual([`foo.vue`])
|
||||||
expect(map!.sourcesContent).toEqual([source])
|
expect(map!.sourcesContent).toEqual([source])
|
||||||
|
|
||||||
const consumer = await new SourceMapConsumer(map as RawSourceMap)
|
const consumer = new SourceMapConsumer(map as RawSourceMap)
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
consumer.originalPositionFor(getPositionInCode(code, `id`))
|
consumer.originalPositionFor(getPositionInCode(code, `id`))
|
||||||
@ -184,7 +184,7 @@ describe('compiler: integration tests', () => {
|
|||||||
).toMatchObject(getPositionInCode(source, `value + index`))
|
).toMatchObject(getPositionInCode(source, `value + index`))
|
||||||
})
|
})
|
||||||
|
|
||||||
test('module mode', async () => {
|
test('module mode', () => {
|
||||||
const { code, map } = compile(source, {
|
const { code, map } = compile(source, {
|
||||||
mode: 'module',
|
mode: 'module',
|
||||||
sourceMap: true,
|
sourceMap: true,
|
||||||
@ -195,7 +195,7 @@ describe('compiler: integration tests', () => {
|
|||||||
expect(map!.sources).toEqual([`foo.vue`])
|
expect(map!.sources).toEqual([`foo.vue`])
|
||||||
expect(map!.sourcesContent).toEqual([source])
|
expect(map!.sourcesContent).toEqual([source])
|
||||||
|
|
||||||
const consumer = await new SourceMapConsumer(map as RawSourceMap)
|
const consumer = new SourceMapConsumer(map as RawSourceMap)
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
consumer.originalPositionFor(getPositionInCode(code, `id`))
|
consumer.originalPositionFor(getPositionInCode(code, `id`))
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { ParserOptions } from '../src/options'
|
import { ParserOptions } from '../src/options'
|
||||||
import { parse, TextModes } from '../src/parse'
|
import { baseParse, TextModes } from '../src/parse'
|
||||||
import { ErrorCodes } from '../src/errors'
|
import { ErrorCodes } from '../src/errors'
|
||||||
import {
|
import {
|
||||||
CommentNode,
|
CommentNode,
|
||||||
@ -16,7 +16,7 @@ import {
|
|||||||
describe('compiler: parse', () => {
|
describe('compiler: parse', () => {
|
||||||
describe('Text', () => {
|
describe('Text', () => {
|
||||||
test('simple text', () => {
|
test('simple text', () => {
|
||||||
const ast = parse('some text')
|
const ast = baseParse('some text')
|
||||||
const text = ast.children[0] as TextNode
|
const text = ast.children[0] as TextNode
|
||||||
|
|
||||||
expect(text).toStrictEqual({
|
expect(text).toStrictEqual({
|
||||||
@ -31,7 +31,7 @@ describe('compiler: parse', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('simple text with invalid end tag', () => {
|
test('simple text with invalid end tag', () => {
|
||||||
const ast = parse('some text</div>', {
|
const ast = baseParse('some text</div>', {
|
||||||
onError: () => {}
|
onError: () => {}
|
||||||
})
|
})
|
||||||
const text = ast.children[0] as TextNode
|
const text = ast.children[0] as TextNode
|
||||||
@ -48,7 +48,7 @@ describe('compiler: parse', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('text with interpolation', () => {
|
test('text with interpolation', () => {
|
||||||
const ast = parse('some {{ foo + bar }} text')
|
const ast = baseParse('some {{ foo + bar }} text')
|
||||||
const text1 = ast.children[0] as TextNode
|
const text1 = ast.children[0] as TextNode
|
||||||
const text2 = ast.children[2] as TextNode
|
const text2 = ast.children[2] as TextNode
|
||||||
|
|
||||||
@ -73,7 +73,7 @@ describe('compiler: parse', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('text with interpolation which has `<`', () => {
|
test('text with interpolation which has `<`', () => {
|
||||||
const ast = parse('some {{ a<b && c>d }} text')
|
const ast = baseParse('some {{ a<b && c>d }} text')
|
||||||
const text1 = ast.children[0] as TextNode
|
const text1 = ast.children[0] as TextNode
|
||||||
const text2 = ast.children[2] as TextNode
|
const text2 = ast.children[2] as TextNode
|
||||||
|
|
||||||
@ -98,7 +98,7 @@ describe('compiler: parse', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('text with mix of tags and interpolations', () => {
|
test('text with mix of tags and interpolations', () => {
|
||||||
const ast = parse('some <span>{{ foo < bar + foo }} text</span>')
|
const ast = baseParse('some <span>{{ foo < bar + foo }} text</span>')
|
||||||
const text1 = ast.children[0] as TextNode
|
const text1 = ast.children[0] as TextNode
|
||||||
const text2 = (ast.children[1] as ElementNode).children![1] as TextNode
|
const text2 = (ast.children[1] as ElementNode).children![1] as TextNode
|
||||||
|
|
||||||
@ -123,7 +123,7 @@ describe('compiler: parse', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('lonly "<" don\'t separate nodes', () => {
|
test('lonly "<" don\'t separate nodes', () => {
|
||||||
const ast = parse('a < b', {
|
const ast = baseParse('a < b', {
|
||||||
onError: err => {
|
onError: err => {
|
||||||
if (err.code !== ErrorCodes.INVALID_FIRST_CHARACTER_OF_TAG_NAME) {
|
if (err.code !== ErrorCodes.INVALID_FIRST_CHARACTER_OF_TAG_NAME) {
|
||||||
throw err
|
throw err
|
||||||
@ -144,7 +144,7 @@ describe('compiler: parse', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('lonly "{{" don\'t separate nodes', () => {
|
test('lonly "{{" don\'t separate nodes', () => {
|
||||||
const ast = parse('a {{ b', {
|
const ast = baseParse('a {{ b', {
|
||||||
onError: error => {
|
onError: error => {
|
||||||
if (error.code !== ErrorCodes.X_MISSING_INTERPOLATION_END) {
|
if (error.code !== ErrorCodes.X_MISSING_INTERPOLATION_END) {
|
||||||
throw error
|
throw error
|
||||||
@ -166,7 +166,7 @@ describe('compiler: parse', () => {
|
|||||||
|
|
||||||
test('HTML entities compatibility in text (https://html.spec.whatwg.org/multipage/parsing.html#named-character-reference-state).', () => {
|
test('HTML entities compatibility in text (https://html.spec.whatwg.org/multipage/parsing.html#named-character-reference-state).', () => {
|
||||||
const spy = jest.fn()
|
const spy = jest.fn()
|
||||||
const ast = parse('&ersand;', {
|
const ast = baseParse('&ersand;', {
|
||||||
namedCharacterReferences: { amp: '&' },
|
namedCharacterReferences: { amp: '&' },
|
||||||
onError: spy
|
onError: spy
|
||||||
})
|
})
|
||||||
@ -195,7 +195,7 @@ describe('compiler: parse', () => {
|
|||||||
|
|
||||||
test('HTML entities compatibility in attribute (https://html.spec.whatwg.org/multipage/parsing.html#named-character-reference-state).', () => {
|
test('HTML entities compatibility in attribute (https://html.spec.whatwg.org/multipage/parsing.html#named-character-reference-state).', () => {
|
||||||
const spy = jest.fn()
|
const spy = jest.fn()
|
||||||
const ast = parse(
|
const ast = baseParse(
|
||||||
'<div a="&ersand;" b="&ersand;" c="&!"></div>',
|
'<div a="&ersand;" b="&ersand;" c="&!"></div>',
|
||||||
{
|
{
|
||||||
namedCharacterReferences: { amp: '&', 'amp;': '&' },
|
namedCharacterReferences: { amp: '&', 'amp;': '&' },
|
||||||
@ -248,7 +248,7 @@ describe('compiler: parse', () => {
|
|||||||
|
|
||||||
test('Some control character reference should be replaced.', () => {
|
test('Some control character reference should be replaced.', () => {
|
||||||
const spy = jest.fn()
|
const spy = jest.fn()
|
||||||
const ast = parse('†', { onError: spy })
|
const ast = baseParse('†', { onError: spy })
|
||||||
const text = ast.children[0] as TextNode
|
const text = ast.children[0] as TextNode
|
||||||
|
|
||||||
expect(text).toStrictEqual({
|
expect(text).toStrictEqual({
|
||||||
@ -275,7 +275,7 @@ describe('compiler: parse', () => {
|
|||||||
|
|
||||||
describe('Interpolation', () => {
|
describe('Interpolation', () => {
|
||||||
test('simple interpolation', () => {
|
test('simple interpolation', () => {
|
||||||
const ast = parse('{{message}}')
|
const ast = baseParse('{{message}}')
|
||||||
const interpolation = ast.children[0] as InterpolationNode
|
const interpolation = ast.children[0] as InterpolationNode
|
||||||
|
|
||||||
expect(interpolation).toStrictEqual({
|
expect(interpolation).toStrictEqual({
|
||||||
@ -300,7 +300,7 @@ describe('compiler: parse', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('it can have tag-like notation', () => {
|
test('it can have tag-like notation', () => {
|
||||||
const ast = parse('{{ a<b }}')
|
const ast = baseParse('{{ a<b }}')
|
||||||
const interpolation = ast.children[0] as InterpolationNode
|
const interpolation = ast.children[0] as InterpolationNode
|
||||||
|
|
||||||
expect(interpolation).toStrictEqual({
|
expect(interpolation).toStrictEqual({
|
||||||
@ -325,7 +325,7 @@ describe('compiler: parse', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('it can have tag-like notation (2)', () => {
|
test('it can have tag-like notation (2)', () => {
|
||||||
const ast = parse('{{ a<b }}{{ c>d }}')
|
const ast = baseParse('{{ a<b }}{{ c>d }}')
|
||||||
const interpolation1 = ast.children[0] as InterpolationNode
|
const interpolation1 = ast.children[0] as InterpolationNode
|
||||||
const interpolation2 = ast.children[1] as InterpolationNode
|
const interpolation2 = ast.children[1] as InterpolationNode
|
||||||
|
|
||||||
@ -371,7 +371,7 @@ describe('compiler: parse', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('it can have tag-like notation (3)', () => {
|
test('it can have tag-like notation (3)', () => {
|
||||||
const ast = parse('<div>{{ "</div>" }}</div>')
|
const ast = baseParse('<div>{{ "</div>" }}</div>')
|
||||||
const element = ast.children[0] as ElementNode
|
const element = ast.children[0] as ElementNode
|
||||||
const interpolation = element.children[0] as InterpolationNode
|
const interpolation = element.children[0] as InterpolationNode
|
||||||
|
|
||||||
@ -398,7 +398,7 @@ describe('compiler: parse', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('custom delimiters', () => {
|
test('custom delimiters', () => {
|
||||||
const ast = parse('<p>{msg}</p>', {
|
const ast = baseParse('<p>{msg}</p>', {
|
||||||
delimiters: ['{', '}']
|
delimiters: ['{', '}']
|
||||||
})
|
})
|
||||||
const element = ast.children[0] as ElementNode
|
const element = ast.children[0] as ElementNode
|
||||||
@ -428,7 +428,7 @@ describe('compiler: parse', () => {
|
|||||||
|
|
||||||
describe('Comment', () => {
|
describe('Comment', () => {
|
||||||
test('empty comment', () => {
|
test('empty comment', () => {
|
||||||
const ast = parse('<!---->')
|
const ast = baseParse('<!---->')
|
||||||
const comment = ast.children[0] as CommentNode
|
const comment = ast.children[0] as CommentNode
|
||||||
|
|
||||||
expect(comment).toStrictEqual({
|
expect(comment).toStrictEqual({
|
||||||
@ -443,7 +443,7 @@ describe('compiler: parse', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('simple comment', () => {
|
test('simple comment', () => {
|
||||||
const ast = parse('<!--abc-->')
|
const ast = baseParse('<!--abc-->')
|
||||||
const comment = ast.children[0] as CommentNode
|
const comment = ast.children[0] as CommentNode
|
||||||
|
|
||||||
expect(comment).toStrictEqual({
|
expect(comment).toStrictEqual({
|
||||||
@ -458,7 +458,7 @@ describe('compiler: parse', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('two comments', () => {
|
test('two comments', () => {
|
||||||
const ast = parse('<!--abc--><!--def-->')
|
const ast = baseParse('<!--abc--><!--def-->')
|
||||||
const comment1 = ast.children[0] as CommentNode
|
const comment1 = ast.children[0] as CommentNode
|
||||||
const comment2 = ast.children[1] as CommentNode
|
const comment2 = ast.children[1] as CommentNode
|
||||||
|
|
||||||
@ -485,7 +485,7 @@ describe('compiler: parse', () => {
|
|||||||
|
|
||||||
describe('Element', () => {
|
describe('Element', () => {
|
||||||
test('simple div', () => {
|
test('simple div', () => {
|
||||||
const ast = parse('<div>hello</div>')
|
const ast = baseParse('<div>hello</div>')
|
||||||
const element = ast.children[0] as ElementNode
|
const element = ast.children[0] as ElementNode
|
||||||
|
|
||||||
expect(element).toStrictEqual({
|
expect(element).toStrictEqual({
|
||||||
@ -516,7 +516,7 @@ describe('compiler: parse', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('empty', () => {
|
test('empty', () => {
|
||||||
const ast = parse('<div></div>')
|
const ast = baseParse('<div></div>')
|
||||||
const element = ast.children[0] as ElementNode
|
const element = ast.children[0] as ElementNode
|
||||||
|
|
||||||
expect(element).toStrictEqual({
|
expect(element).toStrictEqual({
|
||||||
@ -526,7 +526,6 @@ describe('compiler: parse', () => {
|
|||||||
tagType: ElementTypes.ELEMENT,
|
tagType: ElementTypes.ELEMENT,
|
||||||
codegenNode: undefined,
|
codegenNode: undefined,
|
||||||
props: [],
|
props: [],
|
||||||
|
|
||||||
isSelfClosing: false,
|
isSelfClosing: false,
|
||||||
children: [],
|
children: [],
|
||||||
loc: {
|
loc: {
|
||||||
@ -538,7 +537,7 @@ describe('compiler: parse', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('self closing', () => {
|
test('self closing', () => {
|
||||||
const ast = parse('<div/>after')
|
const ast = baseParse('<div/>after')
|
||||||
const element = ast.children[0] as ElementNode
|
const element = ast.children[0] as ElementNode
|
||||||
|
|
||||||
expect(element).toStrictEqual({
|
expect(element).toStrictEqual({
|
||||||
@ -560,7 +559,7 @@ describe('compiler: parse', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('void element', () => {
|
test('void element', () => {
|
||||||
const ast = parse('<img>after', {
|
const ast = baseParse('<img>after', {
|
||||||
isVoidTag: tag => tag === 'img'
|
isVoidTag: tag => tag === 'img'
|
||||||
})
|
})
|
||||||
const element = ast.children[0] as ElementNode
|
const element = ast.children[0] as ElementNode
|
||||||
@ -583,8 +582,26 @@ describe('compiler: parse', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('template element with directives', () => {
|
||||||
|
const ast = baseParse('<template v-if="ok"></template>')
|
||||||
|
const element = ast.children[0]
|
||||||
|
expect(element).toMatchObject({
|
||||||
|
type: NodeTypes.ELEMENT,
|
||||||
|
tagType: ElementTypes.TEMPLATE
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('template element without directives', () => {
|
||||||
|
const ast = baseParse('<template></template>')
|
||||||
|
const element = ast.children[0]
|
||||||
|
expect(element).toMatchObject({
|
||||||
|
type: NodeTypes.ELEMENT,
|
||||||
|
tagType: ElementTypes.ELEMENT
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
test('native element with `isNativeTag`', () => {
|
test('native element with `isNativeTag`', () => {
|
||||||
const ast = parse('<div></div><comp></comp><Comp></Comp>', {
|
const ast = baseParse('<div></div><comp></comp><Comp></Comp>', {
|
||||||
isNativeTag: tag => tag === 'div'
|
isNativeTag: tag => tag === 'div'
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -608,7 +625,7 @@ describe('compiler: parse', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('native element without `isNativeTag`', () => {
|
test('native element without `isNativeTag`', () => {
|
||||||
const ast = parse('<div></div><comp></comp><Comp></Comp>')
|
const ast = baseParse('<div></div><comp></comp><Comp></Comp>')
|
||||||
|
|
||||||
expect(ast.children[0]).toMatchObject({
|
expect(ast.children[0]).toMatchObject({
|
||||||
type: NodeTypes.ELEMENT,
|
type: NodeTypes.ELEMENT,
|
||||||
@ -629,8 +646,57 @@ describe('compiler: parse', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('v-is without `isNativeTag`', () => {
|
||||||
|
const ast = baseParse(
|
||||||
|
`<div></div><div v-is="'foo'"></div><Comp></Comp>`,
|
||||||
|
{
|
||||||
|
isNativeTag: tag => tag === 'div'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(ast.children[0]).toMatchObject({
|
||||||
|
type: NodeTypes.ELEMENT,
|
||||||
|
tag: 'div',
|
||||||
|
tagType: ElementTypes.ELEMENT
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(ast.children[1]).toMatchObject({
|
||||||
|
type: NodeTypes.ELEMENT,
|
||||||
|
tag: 'div',
|
||||||
|
tagType: ElementTypes.COMPONENT
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(ast.children[2]).toMatchObject({
|
||||||
|
type: NodeTypes.ELEMENT,
|
||||||
|
tag: 'Comp',
|
||||||
|
tagType: ElementTypes.COMPONENT
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('v-is with `isNativeTag`', () => {
|
||||||
|
const ast = baseParse(`<div></div><div v-is="'foo'"></div><Comp></Comp>`)
|
||||||
|
|
||||||
|
expect(ast.children[0]).toMatchObject({
|
||||||
|
type: NodeTypes.ELEMENT,
|
||||||
|
tag: 'div',
|
||||||
|
tagType: ElementTypes.ELEMENT
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(ast.children[1]).toMatchObject({
|
||||||
|
type: NodeTypes.ELEMENT,
|
||||||
|
tag: 'div',
|
||||||
|
tagType: ElementTypes.COMPONENT
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(ast.children[2]).toMatchObject({
|
||||||
|
type: NodeTypes.ELEMENT,
|
||||||
|
tag: 'Comp',
|
||||||
|
tagType: ElementTypes.COMPONENT
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
test('custom element', () => {
|
test('custom element', () => {
|
||||||
const ast = parse('<div></div><comp></comp>', {
|
const ast = baseParse('<div></div><comp></comp>', {
|
||||||
isNativeTag: tag => tag === 'div',
|
isNativeTag: tag => tag === 'div',
|
||||||
isCustomElement: tag => tag === 'comp'
|
isCustomElement: tag => tag === 'comp'
|
||||||
})
|
})
|
||||||
@ -649,7 +715,7 @@ describe('compiler: parse', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('attribute with no value', () => {
|
test('attribute with no value', () => {
|
||||||
const ast = parse('<div id></div>')
|
const ast = baseParse('<div id></div>')
|
||||||
const element = ast.children[0] as ElementNode
|
const element = ast.children[0] as ElementNode
|
||||||
|
|
||||||
expect(element).toStrictEqual({
|
expect(element).toStrictEqual({
|
||||||
@ -682,7 +748,7 @@ describe('compiler: parse', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('attribute with empty value, double quote', () => {
|
test('attribute with empty value, double quote', () => {
|
||||||
const ast = parse('<div id=""></div>')
|
const ast = baseParse('<div id=""></div>')
|
||||||
const element = ast.children[0] as ElementNode
|
const element = ast.children[0] as ElementNode
|
||||||
|
|
||||||
expect(element).toStrictEqual({
|
expect(element).toStrictEqual({
|
||||||
@ -723,7 +789,7 @@ describe('compiler: parse', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('attribute with empty value, single quote', () => {
|
test('attribute with empty value, single quote', () => {
|
||||||
const ast = parse("<div id=''></div>")
|
const ast = baseParse("<div id=''></div>")
|
||||||
const element = ast.children[0] as ElementNode
|
const element = ast.children[0] as ElementNode
|
||||||
|
|
||||||
expect(element).toStrictEqual({
|
expect(element).toStrictEqual({
|
||||||
@ -764,7 +830,7 @@ describe('compiler: parse', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('attribute with value, double quote', () => {
|
test('attribute with value, double quote', () => {
|
||||||
const ast = parse('<div id=">\'"></div>')
|
const ast = baseParse('<div id=">\'"></div>')
|
||||||
const element = ast.children[0] as ElementNode
|
const element = ast.children[0] as ElementNode
|
||||||
|
|
||||||
expect(element).toStrictEqual({
|
expect(element).toStrictEqual({
|
||||||
@ -805,7 +871,7 @@ describe('compiler: parse', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('attribute with value, single quote', () => {
|
test('attribute with value, single quote', () => {
|
||||||
const ast = parse("<div id='>\"'></div>")
|
const ast = baseParse("<div id='>\"'></div>")
|
||||||
const element = ast.children[0] as ElementNode
|
const element = ast.children[0] as ElementNode
|
||||||
|
|
||||||
expect(element).toStrictEqual({
|
expect(element).toStrictEqual({
|
||||||
@ -846,7 +912,7 @@ describe('compiler: parse', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('attribute with value, unquoted', () => {
|
test('attribute with value, unquoted', () => {
|
||||||
const ast = parse('<div id=a/></div>')
|
const ast = baseParse('<div id=a/></div>')
|
||||||
const element = ast.children[0] as ElementNode
|
const element = ast.children[0] as ElementNode
|
||||||
|
|
||||||
expect(element).toStrictEqual({
|
expect(element).toStrictEqual({
|
||||||
@ -887,7 +953,7 @@ describe('compiler: parse', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('multiple attributes', () => {
|
test('multiple attributes', () => {
|
||||||
const ast = parse('<div id=a class="c" inert style=\'\'></div>')
|
const ast = baseParse('<div id=a class="c" inert style=\'\'></div>')
|
||||||
const element = ast.children[0] as ElementNode
|
const element = ast.children[0] as ElementNode
|
||||||
|
|
||||||
expect(element).toStrictEqual({
|
expect(element).toStrictEqual({
|
||||||
@ -974,7 +1040,7 @@ describe('compiler: parse', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('directive with no value', () => {
|
test('directive with no value', () => {
|
||||||
const ast = parse('<div v-if/>')
|
const ast = baseParse('<div v-if/>')
|
||||||
const directive = (ast.children[0] as ElementNode).props[0]
|
const directive = (ast.children[0] as ElementNode).props[0]
|
||||||
|
|
||||||
expect(directive).toStrictEqual({
|
expect(directive).toStrictEqual({
|
||||||
@ -992,7 +1058,7 @@ describe('compiler: parse', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('directive with value', () => {
|
test('directive with value', () => {
|
||||||
const ast = parse('<div v-if="a"/>')
|
const ast = baseParse('<div v-if="a"/>')
|
||||||
const directive = (ast.children[0] as ElementNode).props[0]
|
const directive = (ast.children[0] as ElementNode).props[0]
|
||||||
|
|
||||||
expect(directive).toStrictEqual({
|
expect(directive).toStrictEqual({
|
||||||
@ -1020,7 +1086,7 @@ describe('compiler: parse', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('directive with argument', () => {
|
test('directive with argument', () => {
|
||||||
const ast = parse('<div v-on:click/>')
|
const ast = baseParse('<div v-on:click/>')
|
||||||
const directive = (ast.children[0] as ElementNode).props[0]
|
const directive = (ast.children[0] as ElementNode).props[0]
|
||||||
|
|
||||||
expect(directive).toStrictEqual({
|
expect(directive).toStrictEqual({
|
||||||
@ -1057,7 +1123,7 @@ describe('compiler: parse', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('directive with a modifier', () => {
|
test('directive with a modifier', () => {
|
||||||
const ast = parse('<div v-on.enter/>')
|
const ast = baseParse('<div v-on.enter/>')
|
||||||
const directive = (ast.children[0] as ElementNode).props[0]
|
const directive = (ast.children[0] as ElementNode).props[0]
|
||||||
|
|
||||||
expect(directive).toStrictEqual({
|
expect(directive).toStrictEqual({
|
||||||
@ -1075,7 +1141,7 @@ describe('compiler: parse', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('directive with two modifiers', () => {
|
test('directive with two modifiers', () => {
|
||||||
const ast = parse('<div v-on.enter.exact/>')
|
const ast = baseParse('<div v-on.enter.exact/>')
|
||||||
const directive = (ast.children[0] as ElementNode).props[0]
|
const directive = (ast.children[0] as ElementNode).props[0]
|
||||||
|
|
||||||
expect(directive).toStrictEqual({
|
expect(directive).toStrictEqual({
|
||||||
@ -1093,7 +1159,7 @@ describe('compiler: parse', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('directive with argument and modifiers', () => {
|
test('directive with argument and modifiers', () => {
|
||||||
const ast = parse('<div v-on:click.enter.exact/>')
|
const ast = baseParse('<div v-on:click.enter.exact/>')
|
||||||
const directive = (ast.children[0] as ElementNode).props[0]
|
const directive = (ast.children[0] as ElementNode).props[0]
|
||||||
|
|
||||||
expect(directive).toStrictEqual({
|
expect(directive).toStrictEqual({
|
||||||
@ -1130,7 +1196,7 @@ describe('compiler: parse', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('v-bind shorthand', () => {
|
test('v-bind shorthand', () => {
|
||||||
const ast = parse('<div :a=b />')
|
const ast = baseParse('<div :a=b />')
|
||||||
const directive = (ast.children[0] as ElementNode).props[0]
|
const directive = (ast.children[0] as ElementNode).props[0]
|
||||||
|
|
||||||
expect(directive).toStrictEqual({
|
expect(directive).toStrictEqual({
|
||||||
@ -1178,7 +1244,7 @@ describe('compiler: parse', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('v-bind shorthand with modifier', () => {
|
test('v-bind shorthand with modifier', () => {
|
||||||
const ast = parse('<div :a.sync=b />')
|
const ast = baseParse('<div :a.sync=b />')
|
||||||
const directive = (ast.children[0] as ElementNode).props[0]
|
const directive = (ast.children[0] as ElementNode).props[0]
|
||||||
|
|
||||||
expect(directive).toStrictEqual({
|
expect(directive).toStrictEqual({
|
||||||
@ -1226,7 +1292,7 @@ describe('compiler: parse', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('v-on shorthand', () => {
|
test('v-on shorthand', () => {
|
||||||
const ast = parse('<div @a=b />')
|
const ast = baseParse('<div @a=b />')
|
||||||
const directive = (ast.children[0] as ElementNode).props[0]
|
const directive = (ast.children[0] as ElementNode).props[0]
|
||||||
|
|
||||||
expect(directive).toStrictEqual({
|
expect(directive).toStrictEqual({
|
||||||
@ -1274,7 +1340,7 @@ describe('compiler: parse', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('v-on shorthand with modifier', () => {
|
test('v-on shorthand with modifier', () => {
|
||||||
const ast = parse('<div @a.enter=b />')
|
const ast = baseParse('<div @a.enter=b />')
|
||||||
const directive = (ast.children[0] as ElementNode).props[0]
|
const directive = (ast.children[0] as ElementNode).props[0]
|
||||||
|
|
||||||
expect(directive).toStrictEqual({
|
expect(directive).toStrictEqual({
|
||||||
@ -1322,7 +1388,7 @@ describe('compiler: parse', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('v-slot shorthand', () => {
|
test('v-slot shorthand', () => {
|
||||||
const ast = parse('<Comp #a="{ b }" />')
|
const ast = baseParse('<Comp #a="{ b }" />')
|
||||||
const directive = (ast.children[0] as ElementNode).props[0]
|
const directive = (ast.children[0] as ElementNode).props[0]
|
||||||
|
|
||||||
expect(directive).toStrictEqual({
|
expect(directive).toStrictEqual({
|
||||||
@ -1369,7 +1435,7 @@ describe('compiler: parse', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('v-pre', () => {
|
test('v-pre', () => {
|
||||||
const ast = parse(
|
const ast = baseParse(
|
||||||
`<div v-pre :id="foo"><Comp/>{{ bar }}</div>\n` +
|
`<div v-pre :id="foo"><Comp/>{{ bar }}</div>\n` +
|
||||||
`<div :id="foo"><Comp/>{{ bar }}</div>`
|
`<div :id="foo"><Comp/>{{ bar }}</div>`
|
||||||
)
|
)
|
||||||
@ -1451,7 +1517,7 @@ describe('compiler: parse', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('end tags are case-insensitive.', () => {
|
test('end tags are case-insensitive.', () => {
|
||||||
const ast = parse('<div>hello</DIV>after')
|
const ast = baseParse('<div>hello</DIV>after')
|
||||||
const element = ast.children[0] as ElementNode
|
const element = ast.children[0] as ElementNode
|
||||||
const text = element.children[0] as TextNode
|
const text = element.children[0] as TextNode
|
||||||
|
|
||||||
@ -1468,14 +1534,14 @@ describe('compiler: parse', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('self closing single tag', () => {
|
test('self closing single tag', () => {
|
||||||
const ast = parse('<div :class="{ some: condition }" />')
|
const ast = baseParse('<div :class="{ some: condition }" />')
|
||||||
|
|
||||||
expect(ast.children).toHaveLength(1)
|
expect(ast.children).toHaveLength(1)
|
||||||
expect(ast.children[0]).toMatchObject({ tag: 'div' })
|
expect(ast.children[0]).toMatchObject({ tag: 'div' })
|
||||||
})
|
})
|
||||||
|
|
||||||
test('self closing multiple tag', () => {
|
test('self closing multiple tag', () => {
|
||||||
const ast = parse(
|
const ast = baseParse(
|
||||||
`<div :class="{ some: condition }" />\n` +
|
`<div :class="{ some: condition }" />\n` +
|
||||||
`<p v-bind:style="{ color: 'red' }"/>`
|
`<p v-bind:style="{ color: 'red' }"/>`
|
||||||
)
|
)
|
||||||
@ -1488,7 +1554,7 @@ describe('compiler: parse', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('valid html', () => {
|
test('valid html', () => {
|
||||||
const ast = parse(
|
const ast = baseParse(
|
||||||
`<div :class="{ some: condition }">\n` +
|
`<div :class="{ some: condition }">\n` +
|
||||||
` <p v-bind:style="{ color: 'red' }"/>\n` +
|
` <p v-bind:style="{ color: 'red' }"/>\n` +
|
||||||
` <!-- a comment with <html> inside it -->\n` +
|
` <!-- a comment with <html> inside it -->\n` +
|
||||||
@ -1513,11 +1579,11 @@ describe('compiler: parse', () => {
|
|||||||
|
|
||||||
test('invalid html', () => {
|
test('invalid html', () => {
|
||||||
expect(() => {
|
expect(() => {
|
||||||
parse(`<div>\n<span>\n</div>\n</span>`)
|
baseParse(`<div>\n<span>\n</div>\n</span>`)
|
||||||
}).toThrow('End tag was not found. (3:1)')
|
}).toThrow('Element is missing end tag.')
|
||||||
|
|
||||||
const spy = jest.fn()
|
const spy = jest.fn()
|
||||||
const ast = parse(`<div>\n<span>\n</div>\n</span>`, {
|
const ast = baseParse(`<div>\n<span>\n</div>\n</span>`, {
|
||||||
onError: spy
|
onError: spy
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -1527,8 +1593,8 @@ describe('compiler: parse', () => {
|
|||||||
code: ErrorCodes.X_MISSING_END_TAG,
|
code: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: {
|
loc: {
|
||||||
start: {
|
start: {
|
||||||
offset: 13,
|
offset: 6,
|
||||||
line: 3,
|
line: 2,
|
||||||
column: 1
|
column: 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1552,7 +1618,7 @@ describe('compiler: parse', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('parse with correct location info', () => {
|
test('parse with correct location info', () => {
|
||||||
const [foo, bar, but, baz] = parse(
|
const [foo, bar, but, baz] = baseParse(
|
||||||
`
|
`
|
||||||
foo
|
foo
|
||||||
is {{ bar }} but {{ baz }}`.trim()
|
is {{ bar }} but {{ baz }}`.trim()
|
||||||
@ -1588,7 +1654,7 @@ foo
|
|||||||
|
|
||||||
describe('namedCharacterReferences option', () => {
|
describe('namedCharacterReferences option', () => {
|
||||||
test('use the given map', () => {
|
test('use the given map', () => {
|
||||||
const ast: any = parse('&∪︀', {
|
const ast: any = baseParse('&∪︀', {
|
||||||
namedCharacterReferences: {
|
namedCharacterReferences: {
|
||||||
'cups;': '\u222A\uFE00' // UNION with serifs
|
'cups;': '\u222A\uFE00' // UNION with serifs
|
||||||
},
|
},
|
||||||
@ -1603,18 +1669,18 @@ foo
|
|||||||
|
|
||||||
describe('whitespace management', () => {
|
describe('whitespace management', () => {
|
||||||
it('should remove whitespaces at start/end inside an element', () => {
|
it('should remove whitespaces at start/end inside an element', () => {
|
||||||
const ast = parse(`<div> <span/> </div>`)
|
const ast = baseParse(`<div> <span/> </div>`)
|
||||||
expect((ast.children[0] as ElementNode).children.length).toBe(1)
|
expect((ast.children[0] as ElementNode).children.length).toBe(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should remove whitespaces w/ newline between elements', () => {
|
it('should remove whitespaces w/ newline between elements', () => {
|
||||||
const ast = parse(`<div/> \n <div/> \n <div/>`)
|
const ast = baseParse(`<div/> \n <div/> \n <div/>`)
|
||||||
expect(ast.children.length).toBe(3)
|
expect(ast.children.length).toBe(3)
|
||||||
expect(ast.children.every(c => c.type === NodeTypes.ELEMENT)).toBe(true)
|
expect(ast.children.every(c => c.type === NodeTypes.ELEMENT)).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should remove whitespaces adjacent to comments', () => {
|
it('should remove whitespaces adjacent to comments', () => {
|
||||||
const ast = parse(`<div/> \n <!--foo--> <div/>`)
|
const ast = baseParse(`<div/> \n <!--foo--> <div/>`)
|
||||||
expect(ast.children.length).toBe(3)
|
expect(ast.children.length).toBe(3)
|
||||||
expect(ast.children[0].type).toBe(NodeTypes.ELEMENT)
|
expect(ast.children[0].type).toBe(NodeTypes.ELEMENT)
|
||||||
expect(ast.children[1].type).toBe(NodeTypes.COMMENT)
|
expect(ast.children[1].type).toBe(NodeTypes.COMMENT)
|
||||||
@ -1622,7 +1688,7 @@ foo
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should remove whitespaces w/ newline between comments and elements', () => {
|
it('should remove whitespaces w/ newline between comments and elements', () => {
|
||||||
const ast = parse(`<div/> \n <!--foo--> \n <div/>`)
|
const ast = baseParse(`<div/> \n <!--foo--> \n <div/>`)
|
||||||
expect(ast.children.length).toBe(3)
|
expect(ast.children.length).toBe(3)
|
||||||
expect(ast.children[0].type).toBe(NodeTypes.ELEMENT)
|
expect(ast.children[0].type).toBe(NodeTypes.ELEMENT)
|
||||||
expect(ast.children[1].type).toBe(NodeTypes.COMMENT)
|
expect(ast.children[1].type).toBe(NodeTypes.COMMENT)
|
||||||
@ -1630,7 +1696,7 @@ foo
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should NOT remove whitespaces w/ newline between interpolations', () => {
|
it('should NOT remove whitespaces w/ newline between interpolations', () => {
|
||||||
const ast = parse(`{{ foo }} \n {{ bar }}`)
|
const ast = baseParse(`{{ foo }} \n {{ bar }}`)
|
||||||
expect(ast.children.length).toBe(3)
|
expect(ast.children.length).toBe(3)
|
||||||
expect(ast.children[0].type).toBe(NodeTypes.INTERPOLATION)
|
expect(ast.children[0].type).toBe(NodeTypes.INTERPOLATION)
|
||||||
expect(ast.children[1]).toMatchObject({
|
expect(ast.children[1]).toMatchObject({
|
||||||
@ -1641,7 +1707,7 @@ foo
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should NOT remove whitespaces w/o newline between elements', () => {
|
it('should NOT remove whitespaces w/o newline between elements', () => {
|
||||||
const ast = parse(`<div/> <div/> <div/>`)
|
const ast = baseParse(`<div/> <div/> <div/>`)
|
||||||
expect(ast.children.length).toBe(5)
|
expect(ast.children.length).toBe(5)
|
||||||
expect(ast.children.map(c => c.type)).toMatchObject([
|
expect(ast.children.map(c => c.type)).toMatchObject([
|
||||||
NodeTypes.ELEMENT,
|
NodeTypes.ELEMENT,
|
||||||
@ -1653,7 +1719,7 @@ foo
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should condense consecutive whitespaces in text', () => {
|
it('should condense consecutive whitespaces in text', () => {
|
||||||
const ast = parse(` foo \n bar baz `)
|
const ast = baseParse(` foo \n bar baz `)
|
||||||
expect((ast.children[0] as TextNode).content).toBe(` foo bar baz `)
|
expect((ast.children[0] as TextNode).content).toBe(` foo bar baz `)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -1833,7 +1899,7 @@ foo
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: { offset: 11, line: 1, column: 12 }
|
loc: { offset: 0, line: 1, column: 1 }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -1846,7 +1912,7 @@ foo
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: { offset: 12, line: 1, column: 13 }
|
loc: { offset: 0, line: 1, column: 1 }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -1861,11 +1927,11 @@ foo
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: { offset: 29, line: 1, column: 30 }
|
loc: { offset: 10, line: 1, column: 11 }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: { offset: 29, line: 1, column: 30 }
|
loc: { offset: 0, line: 1, column: 1 }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -1878,11 +1944,11 @@ foo
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: { offset: 24, line: 1, column: 25 }
|
loc: { offset: 10, line: 1, column: 11 }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: { offset: 24, line: 1, column: 25 }
|
loc: { offset: 0, line: 1, column: 1 }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -1897,7 +1963,7 @@ foo
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: { offset: 21, line: 1, column: 22 }
|
loc: { offset: 0, line: 1, column: 1 }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -1910,7 +1976,7 @@ foo
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: { offset: 14, line: 1, column: 15 }
|
loc: { offset: 0, line: 1, column: 1 }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -1925,7 +1991,7 @@ foo
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: { offset: 12, line: 1, column: 13 }
|
loc: { offset: 0, line: 1, column: 1 }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -1938,7 +2004,7 @@ foo
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: { offset: 13, line: 1, column: 14 }
|
loc: { offset: 0, line: 1, column: 1 }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -1951,7 +2017,7 @@ foo
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: { offset: 15, line: 1, column: 16 }
|
loc: { offset: 0, line: 1, column: 1 }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -1962,7 +2028,7 @@ foo
|
|||||||
errors: [
|
errors: [
|
||||||
{
|
{
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: { offset: 32, line: 1, column: 33 }
|
loc: { offset: 0, line: 1, column: 1 }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: ErrorCodes.EOF_IN_SCRIPT_HTML_COMMENT_LIKE_TEXT,
|
type: ErrorCodes.EOF_IN_SCRIPT_HTML_COMMENT_LIKE_TEXT,
|
||||||
@ -1975,7 +2041,7 @@ foo
|
|||||||
errors: [
|
errors: [
|
||||||
{
|
{
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: { offset: 28, line: 1, column: 29 }
|
loc: { offset: 0, line: 1, column: 1 }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -1990,11 +2056,11 @@ foo
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: { offset: 14, line: 1, column: 15 }
|
loc: { offset: 10, line: 1, column: 11 }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: { offset: 14, line: 1, column: 15 }
|
loc: { offset: 0, line: 1, column: 1 }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -2007,11 +2073,11 @@ foo
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: { offset: 15, line: 1, column: 16 }
|
loc: { offset: 10, line: 1, column: 11 }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: { offset: 15, line: 1, column: 16 }
|
loc: { offset: 0, line: 1, column: 1 }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -2024,11 +2090,11 @@ foo
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: { offset: 17, line: 1, column: 18 }
|
loc: { offset: 10, line: 1, column: 11 }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: { offset: 17, line: 1, column: 18 }
|
loc: { offset: 0, line: 1, column: 1 }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -2041,11 +2107,11 @@ foo
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: { offset: 18, line: 1, column: 19 }
|
loc: { offset: 10, line: 1, column: 11 }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: { offset: 18, line: 1, column: 19 }
|
loc: { offset: 0, line: 1, column: 1 }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -2062,11 +2128,11 @@ foo
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: { offset: 19, line: 1, column: 20 }
|
loc: { offset: 10, line: 1, column: 11 }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: { offset: 19, line: 1, column: 20 }
|
loc: { offset: 0, line: 1, column: 1 }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -2079,11 +2145,11 @@ foo
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: { offset: 22, line: 1, column: 23 }
|
loc: { offset: 10, line: 1, column: 11 }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: { offset: 22, line: 1, column: 23 }
|
loc: { offset: 0, line: 1, column: 1 }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -2096,11 +2162,11 @@ foo
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: { offset: 22, line: 1, column: 23 }
|
loc: { offset: 10, line: 1, column: 11 }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: { offset: 22, line: 1, column: 23 }
|
loc: { offset: 0, line: 1, column: 1 }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -2113,11 +2179,11 @@ foo
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: { offset: 23, line: 1, column: 24 }
|
loc: { offset: 10, line: 1, column: 11 }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: { offset: 23, line: 1, column: 24 }
|
loc: { offset: 0, line: 1, column: 1 }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -2130,11 +2196,11 @@ foo
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: { offset: 23, line: 1, column: 24 }
|
loc: { offset: 10, line: 1, column: 11 }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: { offset: 23, line: 1, column: 24 }
|
loc: { offset: 0, line: 1, column: 1 }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -2147,11 +2213,11 @@ foo
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: { offset: 21, line: 1, column: 22 }
|
loc: { offset: 10, line: 1, column: 11 }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: { offset: 21, line: 1, column: 22 }
|
loc: { offset: 0, line: 1, column: 1 }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -2168,11 +2234,11 @@ foo
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: { offset: 24, line: 1, column: 25 }
|
loc: { offset: 10, line: 1, column: 11 }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: { offset: 24, line: 1, column: 25 }
|
loc: { offset: 0, line: 1, column: 1 }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -2189,11 +2255,11 @@ foo
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: { offset: 24, line: 1, column: 25 }
|
loc: { offset: 10, line: 1, column: 11 }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: { offset: 24, line: 1, column: 25 }
|
loc: { offset: 0, line: 1, column: 1 }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -2210,11 +2276,11 @@ foo
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: { offset: 23, line: 1, column: 24 }
|
loc: { offset: 10, line: 1, column: 11 }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: { offset: 23, line: 1, column: 24 }
|
loc: { offset: 0, line: 1, column: 1 }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -2292,7 +2358,7 @@ foo
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: { offset: 27, line: 1, column: 28 }
|
loc: { offset: 0, line: 1, column: 1 }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -2429,7 +2495,7 @@ foo
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: { offset: 19, line: 1, column: 20 }
|
loc: { offset: 0, line: 1, column: 1 }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -2594,17 +2660,6 @@ foo
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
UNKNOWN_NAMED_CHARACTER_REFERENCE: [
|
|
||||||
{
|
|
||||||
code: '<template>&unknown;</template>',
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
type: ErrorCodes.UNKNOWN_NAMED_CHARACTER_REFERENCE,
|
|
||||||
loc: { offset: 10, line: 1, column: 11 }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
X_INVALID_END_TAG: [
|
X_INVALID_END_TAG: [
|
||||||
{
|
{
|
||||||
code: '<template></div></template>',
|
code: '<template></div></template>',
|
||||||
@ -2651,7 +2706,7 @@ foo
|
|||||||
errors: [
|
errors: [
|
||||||
{
|
{
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: { offset: 15, line: 1, column: 16 }
|
loc: { offset: 10, line: 1, column: 11 }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -2660,11 +2715,11 @@ foo
|
|||||||
errors: [
|
errors: [
|
||||||
{
|
{
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: { offset: 15, line: 1, column: 16 }
|
loc: { offset: 10, line: 1, column: 11 }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: ErrorCodes.X_MISSING_END_TAG,
|
type: ErrorCodes.X_MISSING_END_TAG,
|
||||||
loc: { offset: 15, line: 1, column: 16 }
|
loc: { offset: 0, line: 1, column: 1 }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -2716,7 +2771,7 @@ foo
|
|||||||
),
|
),
|
||||||
() => {
|
() => {
|
||||||
const spy = jest.fn()
|
const spy = jest.fn()
|
||||||
const ast = parse(code, {
|
const ast = baseParse(code, {
|
||||||
getNamespace: (tag, parent) => {
|
getNamespace: (tag, parent) => {
|
||||||
const ns = parent ? parent.ns : Namespaces.HTML
|
const ns = parent ? parent.ns : Namespaces.HTML
|
||||||
if (ns === Namespaces.HTML) {
|
if (ns === Namespaces.HTML) {
|
||||||
|
97
packages/compiler-core/__tests__/scopeId.spec.ts
Normal file
97
packages/compiler-core/__tests__/scopeId.spec.ts
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
import { baseCompile } from '../src/compile'
|
||||||
|
import {
|
||||||
|
WITH_SCOPE_ID,
|
||||||
|
PUSH_SCOPE_ID,
|
||||||
|
POP_SCOPE_ID
|
||||||
|
} from '../src/runtimeHelpers'
|
||||||
|
import { PatchFlags } from '@vue/shared'
|
||||||
|
import { genFlagText } from './testUtils'
|
||||||
|
|
||||||
|
describe('scopeId compiler support', () => {
|
||||||
|
test('should only work in module mode', () => {
|
||||||
|
expect(() => {
|
||||||
|
baseCompile(``, { scopeId: 'test' })
|
||||||
|
}).toThrow(`"scopeId" option is only supported in module mode`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should wrap render function', () => {
|
||||||
|
const { ast, code } = baseCompile(`<div/>`, {
|
||||||
|
mode: 'module',
|
||||||
|
scopeId: 'test'
|
||||||
|
})
|
||||||
|
expect(ast.helpers).toContain(WITH_SCOPE_ID)
|
||||||
|
expect(code).toMatch(`const _withId = _withScopeId("test")`)
|
||||||
|
expect(code).toMatch(`export const render = _withId(function render(`)
|
||||||
|
expect(code).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should wrap default slot', () => {
|
||||||
|
const { code } = baseCompile(`<Child><div/></Child>`, {
|
||||||
|
mode: 'module',
|
||||||
|
scopeId: 'test'
|
||||||
|
})
|
||||||
|
expect(code).toMatch(`default: _withId(() => [`)
|
||||||
|
expect(code).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should wrap named slots', () => {
|
||||||
|
const { code } = baseCompile(
|
||||||
|
`<Child>
|
||||||
|
<template #foo="{ msg }">{{ msg }}</template>
|
||||||
|
<template #bar><div/></template>
|
||||||
|
</Child>
|
||||||
|
`,
|
||||||
|
{
|
||||||
|
mode: 'module',
|
||||||
|
scopeId: 'test'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
expect(code).toMatch(`foo: _withId(({ msg }) => [`)
|
||||||
|
expect(code).toMatch(`bar: _withId(() => [`)
|
||||||
|
expect(code).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should wrap dynamic slots', () => {
|
||||||
|
const { code } = baseCompile(
|
||||||
|
`<Child>
|
||||||
|
<template #foo v-if="ok"><div/></template>
|
||||||
|
<template v-for="i in list" #[i]><div/></template>
|
||||||
|
</Child>
|
||||||
|
`,
|
||||||
|
{
|
||||||
|
mode: 'module',
|
||||||
|
scopeId: 'test'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
expect(code).toMatch(/name: "foo",\s+fn: _withId\(/)
|
||||||
|
expect(code).toMatch(/name: i,\s+fn: _withId\(/)
|
||||||
|
expect(code).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should push scopeId for hoisted nodes', () => {
|
||||||
|
const { ast, code } = baseCompile(
|
||||||
|
`<div><div>hello</div>{{ foo }}<div>world</div></div>`,
|
||||||
|
{
|
||||||
|
mode: 'module',
|
||||||
|
scopeId: 'test',
|
||||||
|
hoistStatic: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
expect(ast.helpers).toContain(PUSH_SCOPE_ID)
|
||||||
|
expect(ast.helpers).toContain(POP_SCOPE_ID)
|
||||||
|
expect(ast.hoists.length).toBe(2)
|
||||||
|
expect(code).toMatch(
|
||||||
|
[
|
||||||
|
`_pushScopeId("test")`,
|
||||||
|
`const _hoisted_1 = _createVNode("div", null, "hello", ${genFlagText(
|
||||||
|
PatchFlags.HOISTED
|
||||||
|
)})`,
|
||||||
|
`const _hoisted_2 = _createVNode("div", null, "world", ${genFlagText(
|
||||||
|
PatchFlags.HOISTED
|
||||||
|
)})`,
|
||||||
|
`_popScopeId()`
|
||||||
|
].join('\n')
|
||||||
|
)
|
||||||
|
expect(code).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
})
|
@ -4,9 +4,8 @@ import {
|
|||||||
locStub,
|
locStub,
|
||||||
Namespaces,
|
Namespaces,
|
||||||
ElementTypes,
|
ElementTypes,
|
||||||
PlainElementCodegenNode
|
VNodeCall
|
||||||
} from '../src'
|
} from '../src'
|
||||||
import { CREATE_VNODE } from '../src/runtimeHelpers'
|
|
||||||
import { isString, PatchFlags, PatchFlagNames, isArray } from '@vue/shared'
|
import { isString, PatchFlags, PatchFlagNames, isArray } from '@vue/shared'
|
||||||
|
|
||||||
const leadingBracketRE = /^\[/
|
const leadingBracketRE = /^\[/
|
||||||
@ -39,7 +38,11 @@ export function createObjectMatcher(obj: Record<string, any>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function createElementWithCodegen(
|
export function createElementWithCodegen(
|
||||||
args: PlainElementCodegenNode['arguments']
|
tag: VNodeCall['tag'],
|
||||||
|
props?: VNodeCall['props'],
|
||||||
|
children?: VNodeCall['children'],
|
||||||
|
patchFlag?: VNodeCall['patchFlag'],
|
||||||
|
dynamicProps?: VNodeCall['dynamicProps']
|
||||||
): ElementNode {
|
): ElementNode {
|
||||||
return {
|
return {
|
||||||
type: NodeTypes.ELEMENT,
|
type: NodeTypes.ELEMENT,
|
||||||
@ -51,10 +54,16 @@ export function createElementWithCodegen(
|
|||||||
props: [],
|
props: [],
|
||||||
children: [],
|
children: [],
|
||||||
codegenNode: {
|
codegenNode: {
|
||||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
type: NodeTypes.VNODE_CALL,
|
||||||
loc: locStub,
|
tag,
|
||||||
callee: CREATE_VNODE,
|
props,
|
||||||
arguments: args
|
children,
|
||||||
|
patchFlag,
|
||||||
|
dynamicProps,
|
||||||
|
directives: undefined,
|
||||||
|
isBlock: false,
|
||||||
|
isForBlock: false,
|
||||||
|
loc: locStub
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,17 @@
|
|||||||
import { parse } from '../src/parse'
|
import { baseParse } from '../src/parse'
|
||||||
import { transform, NodeTransform } from '../src/transform'
|
import { transform, NodeTransform } from '../src/transform'
|
||||||
import {
|
import {
|
||||||
ElementNode,
|
ElementNode,
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
DirectiveNode,
|
DirectiveNode,
|
||||||
ExpressionNode
|
ExpressionNode,
|
||||||
|
VNodeCall
|
||||||
} from '../src/ast'
|
} from '../src/ast'
|
||||||
import { ErrorCodes, createCompilerError } from '../src/errors'
|
import { ErrorCodes, createCompilerError } from '../src/errors'
|
||||||
import {
|
import {
|
||||||
TO_STRING,
|
TO_DISPLAY_STRING,
|
||||||
OPEN_BLOCK,
|
|
||||||
CREATE_BLOCK,
|
|
||||||
FRAGMENT,
|
FRAGMENT,
|
||||||
RENDER_SLOT,
|
RENDER_SLOT,
|
||||||
WITH_DIRECTIVES,
|
|
||||||
CREATE_COMMENT
|
CREATE_COMMENT
|
||||||
} from '../src/runtimeHelpers'
|
} from '../src/runtimeHelpers'
|
||||||
import { transformIf } from '../src/transforms/vIf'
|
import { transformIf } from '../src/transforms/vIf'
|
||||||
@ -26,7 +24,7 @@ import { PatchFlags } from '@vue/shared'
|
|||||||
|
|
||||||
describe('compiler: transform', () => {
|
describe('compiler: transform', () => {
|
||||||
test('context state', () => {
|
test('context state', () => {
|
||||||
const ast = parse(`<div>hello {{ world }}</div>`)
|
const ast = baseParse(`<div>hello {{ world }}</div>`)
|
||||||
|
|
||||||
// manually store call arguments because context is mutable and shared
|
// manually store call arguments because context is mutable and shared
|
||||||
// across calls
|
// across calls
|
||||||
@ -72,7 +70,7 @@ describe('compiler: transform', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('context.replaceNode', () => {
|
test('context.replaceNode', () => {
|
||||||
const ast = parse(`<div/><span/>`)
|
const ast = baseParse(`<div/><span/>`)
|
||||||
const plugin: NodeTransform = (node, context) => {
|
const plugin: NodeTransform = (node, context) => {
|
||||||
if (node.type === NodeTypes.ELEMENT && node.tag === 'div') {
|
if (node.type === NodeTypes.ELEMENT && node.tag === 'div') {
|
||||||
// change the node to <p>
|
// change the node to <p>
|
||||||
@ -106,7 +104,7 @@ describe('compiler: transform', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('context.removeNode', () => {
|
test('context.removeNode', () => {
|
||||||
const ast = parse(`<span/><div>hello</div><span/>`)
|
const ast = baseParse(`<span/><div>hello</div><span/>`)
|
||||||
const c1 = ast.children[0]
|
const c1 = ast.children[0]
|
||||||
const c2 = ast.children[2]
|
const c2 = ast.children[2]
|
||||||
|
|
||||||
@ -132,7 +130,7 @@ describe('compiler: transform', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('context.removeNode (prev sibling)', () => {
|
test('context.removeNode (prev sibling)', () => {
|
||||||
const ast = parse(`<span/><div/><span/>`)
|
const ast = baseParse(`<span/><div/><span/>`)
|
||||||
const c1 = ast.children[0]
|
const c1 = ast.children[0]
|
||||||
const c2 = ast.children[2]
|
const c2 = ast.children[2]
|
||||||
|
|
||||||
@ -159,7 +157,7 @@ describe('compiler: transform', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('context.removeNode (next sibling)', () => {
|
test('context.removeNode (next sibling)', () => {
|
||||||
const ast = parse(`<span/><div/><span/>`)
|
const ast = baseParse(`<span/><div/><span/>`)
|
||||||
const c1 = ast.children[0]
|
const c1 = ast.children[0]
|
||||||
const d1 = ast.children[1]
|
const d1 = ast.children[1]
|
||||||
|
|
||||||
@ -186,7 +184,7 @@ describe('compiler: transform', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('context.hoist', () => {
|
test('context.hoist', () => {
|
||||||
const ast = parse(`<div :id="foo"/><div :id="bar"/>`)
|
const ast = baseParse(`<div :id="foo"/><div :id="bar"/>`)
|
||||||
const hoisted: ExpressionNode[] = []
|
const hoisted: ExpressionNode[] = []
|
||||||
const mock: NodeTransform = (node, context) => {
|
const mock: NodeTransform = (node, context) => {
|
||||||
if (node.type === NodeTypes.ELEMENT) {
|
if (node.type === NodeTypes.ELEMENT) {
|
||||||
@ -204,7 +202,7 @@ describe('compiler: transform', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('onError option', () => {
|
test('onError option', () => {
|
||||||
const ast = parse(`<div/>`)
|
const ast = baseParse(`<div/>`)
|
||||||
const loc = ast.children[0].loc
|
const loc = ast.children[0].loc
|
||||||
const plugin: NodeTransform = (node, context) => {
|
const plugin: NodeTransform = (node, context) => {
|
||||||
context.onError(
|
context.onError(
|
||||||
@ -225,20 +223,20 @@ describe('compiler: transform', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('should inject toString helper for interpolations', () => {
|
test('should inject toString helper for interpolations', () => {
|
||||||
const ast = parse(`{{ foo }}`)
|
const ast = baseParse(`{{ foo }}`)
|
||||||
transform(ast, {})
|
transform(ast, {})
|
||||||
expect(ast.helpers).toContain(TO_STRING)
|
expect(ast.helpers).toContain(TO_DISPLAY_STRING)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should inject createVNode and Comment for comments', () => {
|
test('should inject createVNode and Comment for comments', () => {
|
||||||
const ast = parse(`<!--foo-->`)
|
const ast = baseParse(`<!--foo-->`)
|
||||||
transform(ast, {})
|
transform(ast, {})
|
||||||
expect(ast.helpers).toContain(CREATE_COMMENT)
|
expect(ast.helpers).toContain(CREATE_COMMENT)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('root codegenNode', () => {
|
describe('root codegenNode', () => {
|
||||||
function transformWithCodegen(template: string) {
|
function transformWithCodegen(template: string) {
|
||||||
const ast = parse(template)
|
const ast = baseParse(template)
|
||||||
transform(ast, {
|
transform(ast, {
|
||||||
nodeTransforms: [
|
nodeTransforms: [
|
||||||
transformIf,
|
transformIf,
|
||||||
@ -251,20 +249,19 @@ describe('compiler: transform', () => {
|
|||||||
return ast
|
return ast
|
||||||
}
|
}
|
||||||
|
|
||||||
function createBlockMatcher(args: any[]) {
|
function createBlockMatcher(
|
||||||
|
tag: VNodeCall['tag'],
|
||||||
|
props?: VNodeCall['props'],
|
||||||
|
children?: VNodeCall['children'],
|
||||||
|
patchFlag?: VNodeCall['patchFlag']
|
||||||
|
) {
|
||||||
return {
|
return {
|
||||||
type: NodeTypes.JS_SEQUENCE_EXPRESSION,
|
type: NodeTypes.VNODE_CALL,
|
||||||
expressions: [
|
isBlock: true,
|
||||||
{
|
tag,
|
||||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
props,
|
||||||
callee: OPEN_BLOCK
|
children,
|
||||||
},
|
patchFlag
|
||||||
{
|
|
||||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
|
||||||
callee: CREATE_BLOCK,
|
|
||||||
arguments: args
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -285,7 +282,7 @@ describe('compiler: transform', () => {
|
|||||||
|
|
||||||
test('single element', () => {
|
test('single element', () => {
|
||||||
const ast = transformWithCodegen(`<div/>`)
|
const ast = transformWithCodegen(`<div/>`)
|
||||||
expect(ast.codegenNode).toMatchObject(createBlockMatcher([`"div"`]))
|
expect(ast.codegenNode).toMatchObject(createBlockMatcher(`"div"`))
|
||||||
})
|
})
|
||||||
|
|
||||||
test('root v-if', () => {
|
test('root v-if', () => {
|
||||||
@ -305,22 +302,8 @@ describe('compiler: transform', () => {
|
|||||||
test('root element with custom directive', () => {
|
test('root element with custom directive', () => {
|
||||||
const ast = transformWithCodegen(`<div v-foo/>`)
|
const ast = transformWithCodegen(`<div v-foo/>`)
|
||||||
expect(ast.codegenNode).toMatchObject({
|
expect(ast.codegenNode).toMatchObject({
|
||||||
type: NodeTypes.JS_SEQUENCE_EXPRESSION,
|
type: NodeTypes.VNODE_CALL,
|
||||||
expressions: [
|
directives: { type: NodeTypes.JS_ARRAY_EXPRESSION }
|
||||||
{
|
|
||||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
|
||||||
callee: OPEN_BLOCK
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
|
||||||
// should wrap withDirectives() around createBlock()
|
|
||||||
callee: WITH_DIRECTIVES,
|
|
||||||
arguments: [
|
|
||||||
{ callee: CREATE_BLOCK },
|
|
||||||
{ type: NodeTypes.JS_ARRAY_EXPRESSION }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -348,15 +331,15 @@ describe('compiler: transform', () => {
|
|||||||
test('multiple children', () => {
|
test('multiple children', () => {
|
||||||
const ast = transformWithCodegen(`<div/><div/>`)
|
const ast = transformWithCodegen(`<div/><div/>`)
|
||||||
expect(ast.codegenNode).toMatchObject(
|
expect(ast.codegenNode).toMatchObject(
|
||||||
createBlockMatcher([
|
createBlockMatcher(
|
||||||
FRAGMENT,
|
FRAGMENT,
|
||||||
`null`,
|
undefined,
|
||||||
[
|
[
|
||||||
{ type: NodeTypes.ELEMENT, tag: `div` },
|
{ type: NodeTypes.ELEMENT, tag: `div` },
|
||||||
{ type: NodeTypes.ELEMENT, tag: `div` }
|
{ type: NodeTypes.ELEMENT, tag: `div` }
|
||||||
],
|
] as any,
|
||||||
genFlagText(PatchFlags.STABLE_FRAGMENT)
|
genFlagText(PatchFlags.STABLE_FRAGMENT)
|
||||||
])
|
)
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
exports[`compiler: hoistStatic transform hoist element with static key 1`] = `
|
exports[`compiler: hoistStatic transform hoist element with static key 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
const { createVNode: _createVNode } = Vue
|
const { createVNode: _createVNode } = _Vue
|
||||||
|
|
||||||
const _hoisted_1 = _createVNode(\\"div\\", { key: \\"foo\\" })
|
const _hoisted_1 = _createVNode(\\"div\\", { key: \\"foo\\" }, null, -1 /* HOISTED */)
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||||
|
|
||||||
return (_openBlock(), _createBlock(\\"div\\", null, [
|
return (_openBlock(), _createBlock(\\"div\\", null, [
|
||||||
_hoisted_1
|
_hoisted_1
|
||||||
@ -19,16 +19,16 @@ return function render() {
|
|||||||
|
|
||||||
exports[`compiler: hoistStatic transform hoist nested static tree 1`] = `
|
exports[`compiler: hoistStatic transform hoist nested static tree 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
const { createVNode: _createVNode } = Vue
|
const { createVNode: _createVNode } = _Vue
|
||||||
|
|
||||||
const _hoisted_1 = _createVNode(\\"p\\", null, [
|
const _hoisted_1 = _createVNode(\\"p\\", null, [
|
||||||
_createVNode(\\"span\\"),
|
_createVNode(\\"span\\"),
|
||||||
_createVNode(\\"span\\")
|
_createVNode(\\"span\\")
|
||||||
])
|
], -1 /* HOISTED */)
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||||
|
|
||||||
return (_openBlock(), _createBlock(\\"div\\", null, [
|
return (_openBlock(), _createBlock(\\"div\\", null, [
|
||||||
_hoisted_1
|
_hoisted_1
|
||||||
@ -39,15 +39,15 @@ return function render() {
|
|||||||
|
|
||||||
exports[`compiler: hoistStatic transform hoist nested static tree with comments 1`] = `
|
exports[`compiler: hoistStatic transform hoist nested static tree with comments 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
const { createVNode: _createVNode, createCommentVNode: _createCommentVNode } = Vue
|
const { createVNode: _createVNode, createCommentVNode: _createCommentVNode } = _Vue
|
||||||
|
|
||||||
const _hoisted_1 = _createVNode(\\"div\\", null, [
|
const _hoisted_1 = _createVNode(\\"div\\", null, [
|
||||||
_createCommentVNode(\\"comment\\")
|
_createCommentVNode(\\"comment\\")
|
||||||
])
|
], -1 /* HOISTED */)
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { createCommentVNode: _createCommentVNode, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
const { createCommentVNode: _createCommentVNode, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||||
|
|
||||||
return (_openBlock(), _createBlock(\\"div\\", null, [
|
return (_openBlock(), _createBlock(\\"div\\", null, [
|
||||||
_hoisted_1
|
_hoisted_1
|
||||||
@ -58,14 +58,14 @@ return function render() {
|
|||||||
|
|
||||||
exports[`compiler: hoistStatic transform hoist siblings with common non-hoistable parent 1`] = `
|
exports[`compiler: hoistStatic transform hoist siblings with common non-hoistable parent 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
const { createVNode: _createVNode } = Vue
|
const { createVNode: _createVNode } = _Vue
|
||||||
|
|
||||||
const _hoisted_1 = _createVNode(\\"span\\")
|
const _hoisted_1 = _createVNode(\\"span\\", null, null, -1 /* HOISTED */)
|
||||||
const _hoisted_2 = _createVNode(\\"div\\")
|
const _hoisted_2 = _createVNode(\\"div\\", null, null, -1 /* HOISTED */)
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||||
|
|
||||||
return (_openBlock(), _createBlock(\\"div\\", null, [
|
return (_openBlock(), _createBlock(\\"div\\", null, [
|
||||||
_hoisted_1,
|
_hoisted_1,
|
||||||
@ -77,13 +77,13 @@ return function render() {
|
|||||||
|
|
||||||
exports[`compiler: hoistStatic transform hoist simple element 1`] = `
|
exports[`compiler: hoistStatic transform hoist simple element 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
const { createVNode: _createVNode } = Vue
|
const { createVNode: _createVNode } = _Vue
|
||||||
|
|
||||||
const _hoisted_1 = _createVNode(\\"span\\", { class: \\"inline\\" }, \\"hello\\")
|
const _hoisted_1 = _createVNode(\\"span\\", { class: \\"inline\\" }, \\"hello\\", -1 /* HOISTED */)
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||||
|
|
||||||
return (_openBlock(), _createBlock(\\"div\\", null, [
|
return (_openBlock(), _createBlock(\\"div\\", null, [
|
||||||
_hoisted_1
|
_hoisted_1
|
||||||
@ -94,18 +94,18 @@ return function render() {
|
|||||||
|
|
||||||
exports[`compiler: hoistStatic transform hoist static props for elements with directives 1`] = `
|
exports[`compiler: hoistStatic transform hoist static props for elements with directives 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
const { createVNode: _createVNode } = Vue
|
const { createVNode: _createVNode } = _Vue
|
||||||
|
|
||||||
const _hoisted_1 = { id: \\"foo\\" }
|
const _hoisted_1 = { id: \\"foo\\" }
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { createVNode: _createVNode, withDirectives: _withDirectives, resolveDirective: _resolveDirective, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
const { resolveDirective: _resolveDirective, createVNode: _createVNode, withDirectives: _withDirectives, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||||
|
|
||||||
const _directive_foo = _resolveDirective(\\"foo\\")
|
const _directive_foo = _resolveDirective(\\"foo\\")
|
||||||
|
|
||||||
return (_openBlock(), _createBlock(\\"div\\", null, [
|
return (_openBlock(), _createBlock(\\"div\\", null, [
|
||||||
_withDirectives(_createVNode(\\"div\\", _hoisted_1, null, 32 /* NEED_PATCH */), [
|
_withDirectives(_createVNode(\\"div\\", _hoisted_1, null, 512 /* NEED_PATCH */), [
|
||||||
[_directive_foo]
|
[_directive_foo]
|
||||||
])
|
])
|
||||||
]))
|
]))
|
||||||
@ -115,16 +115,16 @@ return function render() {
|
|||||||
|
|
||||||
exports[`compiler: hoistStatic transform hoist static props for elements with dynamic text children 1`] = `
|
exports[`compiler: hoistStatic transform hoist static props for elements with dynamic text children 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
const { createVNode: _createVNode } = Vue
|
const { createVNode: _createVNode } = _Vue
|
||||||
|
|
||||||
const _hoisted_1 = { id: \\"foo\\" }
|
const _hoisted_1 = { id: \\"foo\\" }
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { toString: _toString, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
const { toDisplayString: _toDisplayString, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||||
|
|
||||||
return (_openBlock(), _createBlock(\\"div\\", null, [
|
return (_openBlock(), _createBlock(\\"div\\", null, [
|
||||||
_createVNode(\\"div\\", _hoisted_1, _toString(hello), 1 /* TEXT */)
|
_createVNode(\\"div\\", _hoisted_1, _toDisplayString(hello), 1 /* TEXT */)
|
||||||
]))
|
]))
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@ -132,13 +132,13 @@ return function render() {
|
|||||||
|
|
||||||
exports[`compiler: hoistStatic transform hoist static props for elements with unhoistable children 1`] = `
|
exports[`compiler: hoistStatic transform hoist static props for elements with unhoistable children 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
const { createVNode: _createVNode } = Vue
|
const { createVNode: _createVNode } = _Vue
|
||||||
|
|
||||||
const _hoisted_1 = { id: \\"foo\\" }
|
const _hoisted_1 = { id: \\"foo\\" }
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { resolveComponent: _resolveComponent, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
const { resolveComponent: _resolveComponent, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||||
|
|
||||||
const _component_Comp = _resolveComponent(\\"Comp\\")
|
const _component_Comp = _resolveComponent(\\"Comp\\")
|
||||||
|
|
||||||
@ -153,16 +153,16 @@ return function render() {
|
|||||||
|
|
||||||
exports[`compiler: hoistStatic transform prefixIdentifiers hoist class with static object value 1`] = `
|
exports[`compiler: hoistStatic transform prefixIdentifiers hoist class with static object value 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
const { createVNode: _createVNode } = Vue
|
const { createVNode: _createVNode } = _Vue
|
||||||
|
|
||||||
const _hoisted_1 = { class: { foo: true } }
|
const _hoisted_1 = { class: { foo: true } }
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { toString: _toString, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
const { toDisplayString: _toDisplayString, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||||
|
|
||||||
return (_openBlock(), _createBlock(\\"div\\", null, [
|
return (_openBlock(), _createBlock(\\"div\\", null, [
|
||||||
_createVNode(\\"span\\", _hoisted_1, _toString(_ctx.bar), 1 /* TEXT */)
|
_createVNode(\\"span\\", _hoisted_1, _toDisplayString(_ctx.bar), 1 /* TEXT */)
|
||||||
]))
|
]))
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@ -170,18 +170,13 @@ return function render() {
|
|||||||
|
|
||||||
exports[`compiler: hoistStatic transform prefixIdentifiers hoist nested static tree with static interpolation 1`] = `
|
exports[`compiler: hoistStatic transform prefixIdentifiers hoist nested static tree with static interpolation 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
const { createVNode: _createVNode } = Vue
|
const { createVNode: _createVNode } = _Vue
|
||||||
|
|
||||||
const _hoisted_1 = _createVNode(\\"span\\", null, [
|
const _hoisted_1 = _createVNode(\\"span\\", null, \\"foo \\" + _toDisplayString(1) + \\" \\" + _toDisplayString(true), -1 /* HOISTED */)
|
||||||
\\"foo \\",
|
|
||||||
_toString(1),
|
|
||||||
\\" \\",
|
|
||||||
_toString(true)
|
|
||||||
])
|
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { toString: _toString, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
const { toDisplayString: _toDisplayString, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||||
|
|
||||||
return (_openBlock(), _createBlock(\\"div\\", null, [
|
return (_openBlock(), _createBlock(\\"div\\", null, [
|
||||||
_hoisted_1
|
_hoisted_1
|
||||||
@ -192,13 +187,13 @@ return function render() {
|
|||||||
|
|
||||||
exports[`compiler: hoistStatic transform prefixIdentifiers hoist nested static tree with static prop value 1`] = `
|
exports[`compiler: hoistStatic transform prefixIdentifiers hoist nested static tree with static prop value 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
const { createVNode: _createVNode } = Vue
|
const { createVNode: _createVNode } = _Vue
|
||||||
|
|
||||||
const _hoisted_1 = _createVNode(\\"span\\", { foo: 0 }, _toString(1))
|
const _hoisted_1 = _createVNode(\\"span\\", { foo: 0 }, _toDisplayString(1), -1 /* HOISTED */)
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { toString: _toString, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
const { toDisplayString: _toDisplayString, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||||
|
|
||||||
return (_openBlock(), _createBlock(\\"div\\", null, [
|
return (_openBlock(), _createBlock(\\"div\\", null, [
|
||||||
_hoisted_1
|
_hoisted_1
|
||||||
@ -208,14 +203,12 @@ return function render() {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`compiler: hoistStatic transform prefixIdentifiers should NOT hoist elements with cached handlers 1`] = `
|
exports[`compiler: hoistStatic transform prefixIdentifiers should NOT hoist elements with cached handlers 1`] = `
|
||||||
"import { createVNode, createBlock, openBlock } from \\"vue\\"
|
"import { createVNode as _createVNode, openBlock as _openBlock, createBlock as _createBlock } from \\"vue\\"
|
||||||
|
|
||||||
export default function render() {
|
export function render(_ctx, _cache) {
|
||||||
const _ctx = this
|
return (_openBlock(), _createBlock(\\"div\\", null, [
|
||||||
const _cache = _ctx.$cache
|
_createVNode(\\"div\\", null, [
|
||||||
return (openBlock(), createBlock(\\"div\\", null, [
|
_createVNode(\\"div\\", {
|
||||||
createVNode(\\"div\\", null, [
|
|
||||||
createVNode(\\"div\\", {
|
|
||||||
onClick: _cache[1] || (_cache[1] = $event => (_ctx.foo($event)))
|
onClick: _cache[1] || (_cache[1] = $event => (_ctx.foo($event)))
|
||||||
})
|
})
|
||||||
])
|
])
|
||||||
@ -226,14 +219,14 @@ export default function render() {
|
|||||||
exports[`compiler: hoistStatic transform prefixIdentifiers should NOT hoist expressions that refer scope variables (2) 1`] = `
|
exports[`compiler: hoistStatic transform prefixIdentifiers should NOT hoist expressions that refer scope variables (2) 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { renderList: _renderList, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, toString: _toString, createVNode: _createVNode } = _Vue
|
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, toDisplayString: _toDisplayString, createVNode: _createVNode } = _Vue
|
||||||
|
|
||||||
return (_openBlock(), _createBlock(\\"div\\", null, [
|
return (_openBlock(), _createBlock(\\"div\\", null, [
|
||||||
(_openBlock(false), _createBlock(_Fragment, null, _renderList(_ctx.list, (o) => {
|
(_openBlock(true), _createBlock(_Fragment, null, _renderList(_ctx.list, (o) => {
|
||||||
return (_openBlock(), _createBlock(\\"p\\", null, [
|
return (_openBlock(), _createBlock(\\"p\\", null, [
|
||||||
_createVNode(\\"span\\", null, _toString(o + 'foo'), 1 /* TEXT */)
|
_createVNode(\\"span\\", null, _toDisplayString(o + 'foo'), 1 /* TEXT */)
|
||||||
]))
|
]))
|
||||||
}), 256 /* UNKEYED_FRAGMENT */))
|
}), 256 /* UNKEYED_FRAGMENT */))
|
||||||
]))
|
]))
|
||||||
@ -244,15 +237,17 @@ return function render() {
|
|||||||
exports[`compiler: hoistStatic transform prefixIdentifiers should NOT hoist expressions that refer scope variables (v-slot) 1`] = `
|
exports[`compiler: hoistStatic transform prefixIdentifiers should NOT hoist expressions that refer scope variables (v-slot) 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { toString: _toString, resolveComponent: _resolveComponent, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
const { toDisplayString: _toDisplayString, createTextVNode: _createTextVNode, resolveComponent: _resolveComponent, withCtx: _withCtx, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||||
|
|
||||||
const _component_Comp = _resolveComponent(\\"Comp\\")
|
const _component_Comp = _resolveComponent(\\"Comp\\")
|
||||||
|
|
||||||
return (_openBlock(), _createBlock(_component_Comp, null, {
|
return (_openBlock(), _createBlock(_component_Comp, null, {
|
||||||
default: ({ foo }) => [_toString(_ctx.foo)],
|
default: _withCtx(({ foo }) => [
|
||||||
_compiled: true
|
_createTextVNode(_toDisplayString(_ctx.foo), 1 /* TEXT */)
|
||||||
|
]),
|
||||||
|
_: 1
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@ -261,14 +256,14 @@ return function render() {
|
|||||||
exports[`compiler: hoistStatic transform prefixIdentifiers should NOT hoist expressions that refer scope variables 1`] = `
|
exports[`compiler: hoistStatic transform prefixIdentifiers should NOT hoist expressions that refer scope variables 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { renderList: _renderList, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, toString: _toString, createVNode: _createVNode } = _Vue
|
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, toDisplayString: _toDisplayString, createVNode: _createVNode } = _Vue
|
||||||
|
|
||||||
return (_openBlock(), _createBlock(\\"div\\", null, [
|
return (_openBlock(), _createBlock(\\"div\\", null, [
|
||||||
(_openBlock(false), _createBlock(_Fragment, null, _renderList(_ctx.list, (o) => {
|
(_openBlock(true), _createBlock(_Fragment, null, _renderList(_ctx.list, (o) => {
|
||||||
return (_openBlock(), _createBlock(\\"p\\", null, [
|
return (_openBlock(), _createBlock(\\"p\\", null, [
|
||||||
_createVNode(\\"span\\", null, _toString(o), 1 /* TEXT */)
|
_createVNode(\\"span\\", null, _toDisplayString(o), 1 /* TEXT */)
|
||||||
]))
|
]))
|
||||||
}), 256 /* UNKEYED_FRAGMENT */))
|
}), 256 /* UNKEYED_FRAGMENT */))
|
||||||
]))
|
]))
|
||||||
@ -279,9 +274,9 @@ return function render() {
|
|||||||
exports[`compiler: hoistStatic transform should NOT hoist components 1`] = `
|
exports[`compiler: hoistStatic transform should NOT hoist components 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { resolveComponent: _resolveComponent, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
const { resolveComponent: _resolveComponent, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||||
|
|
||||||
const _component_Comp = _resolveComponent(\\"Comp\\")
|
const _component_Comp = _resolveComponent(\\"Comp\\")
|
||||||
|
|
||||||
@ -295,12 +290,12 @@ return function render() {
|
|||||||
exports[`compiler: hoistStatic transform should NOT hoist element with dynamic key 1`] = `
|
exports[`compiler: hoistStatic transform should NOT hoist element with dynamic key 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
const { openBlock: _openBlock, createBlock: _createBlock, createVNode: _createVNode } = _Vue
|
||||||
|
|
||||||
return (_openBlock(), _createBlock(\\"div\\", null, [
|
return (_openBlock(), _createBlock(\\"div\\", null, [
|
||||||
_createVNode(\\"div\\", { key: foo })
|
(_openBlock(), _createBlock(\\"div\\", { key: foo }))
|
||||||
]))
|
]))
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@ -309,9 +304,9 @@ return function render() {
|
|||||||
exports[`compiler: hoistStatic transform should NOT hoist element with dynamic props 1`] = `
|
exports[`compiler: hoistStatic transform should NOT hoist element with dynamic props 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||||
|
|
||||||
return (_openBlock(), _createBlock(\\"div\\", null, [
|
return (_openBlock(), _createBlock(\\"div\\", null, [
|
||||||
_createVNode(\\"div\\", { id: foo }, null, 8 /* PROPS */, [\\"id\\"])
|
_createVNode(\\"div\\", { id: foo }, null, 8 /* PROPS */, [\\"id\\"])
|
||||||
@ -323,12 +318,12 @@ return function render() {
|
|||||||
exports[`compiler: hoistStatic transform should NOT hoist element with dynamic ref 1`] = `
|
exports[`compiler: hoistStatic transform should NOT hoist element with dynamic ref 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||||
|
|
||||||
return (_openBlock(), _createBlock(\\"div\\", null, [
|
return (_openBlock(), _createBlock(\\"div\\", null, [
|
||||||
_createVNode(\\"div\\", { ref: foo }, null, 32 /* NEED_PATCH */)
|
_createVNode(\\"div\\", { ref: foo }, null, 512 /* NEED_PATCH */)
|
||||||
]))
|
]))
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@ -337,9 +332,9 @@ return function render() {
|
|||||||
exports[`compiler: hoistStatic transform should NOT hoist root node 1`] = `
|
exports[`compiler: hoistStatic transform should NOT hoist root node 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||||
|
|
||||||
return (_openBlock(), _createBlock(\\"div\\"))
|
return (_openBlock(), _createBlock(\\"div\\"))
|
||||||
}
|
}
|
||||||
@ -348,17 +343,17 @@ return function render() {
|
|||||||
|
|
||||||
exports[`compiler: hoistStatic transform should hoist v-for children if static 1`] = `
|
exports[`compiler: hoistStatic transform should hoist v-for children if static 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
const { createVNode: _createVNode } = Vue
|
const { createVNode: _createVNode } = _Vue
|
||||||
|
|
||||||
const _hoisted_1 = { id: \\"foo\\" }
|
const _hoisted_1 = { id: \\"foo\\" }
|
||||||
const _hoisted_2 = _createVNode(\\"span\\")
|
const _hoisted_2 = _createVNode(\\"span\\", null, null, -1 /* HOISTED */)
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { renderList: _renderList, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, createVNode: _createVNode } = _Vue
|
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, createVNode: _createVNode } = _Vue
|
||||||
|
|
||||||
return (_openBlock(), _createBlock(\\"div\\", null, [
|
return (_openBlock(), _createBlock(\\"div\\", null, [
|
||||||
(_openBlock(false), _createBlock(_Fragment, null, _renderList(list, (i) => {
|
(_openBlock(true), _createBlock(_Fragment, null, _renderList(list, (i) => {
|
||||||
return (_openBlock(), _createBlock(\\"div\\", _hoisted_1, [
|
return (_openBlock(), _createBlock(\\"div\\", _hoisted_1, [
|
||||||
_hoisted_2
|
_hoisted_2
|
||||||
]))
|
]))
|
||||||
@ -370,24 +365,24 @@ return function render() {
|
|||||||
|
|
||||||
exports[`compiler: hoistStatic transform should hoist v-if props/children if static 1`] = `
|
exports[`compiler: hoistStatic transform should hoist v-if props/children if static 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
const { createVNode: _createVNode, createCommentVNode: _createCommentVNode } = Vue
|
const { createVNode: _createVNode, createCommentVNode: _createCommentVNode } = _Vue
|
||||||
|
|
||||||
const _hoisted_1 = {
|
const _hoisted_1 = {
|
||||||
key: 0,
|
key: 0,
|
||||||
id: \\"foo\\"
|
id: \\"foo\\"
|
||||||
}
|
}
|
||||||
const _hoisted_2 = _createVNode(\\"span\\")
|
const _hoisted_2 = _createVNode(\\"span\\", null, null, -1 /* HOISTED */)
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, createCommentVNode: _createCommentVNode } = _Vue
|
const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock, createCommentVNode: _createCommentVNode } = _Vue
|
||||||
|
|
||||||
return (_openBlock(), _createBlock(\\"div\\", null, [
|
return (_openBlock(), _createBlock(\\"div\\", null, [
|
||||||
(_openBlock(), ok
|
ok
|
||||||
? _createBlock(\\"div\\", _hoisted_1, [
|
? (_openBlock(), _createBlock(\\"div\\", _hoisted_1, [
|
||||||
_hoisted_2
|
_hoisted_2
|
||||||
])
|
]))
|
||||||
: _createCommentVNode(\\"v-if\\", true))
|
: _createCommentVNode(\\"v-if\\", true)
|
||||||
]))
|
]))
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
|
@ -3,11 +3,11 @@
|
|||||||
exports[`compiler: transform text <template v-for> 1`] = `
|
exports[`compiler: transform text <template v-for> 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { renderList: _renderList, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, createTextVNode: _createTextVNode } = _Vue
|
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, createTextVNode: _createTextVNode } = _Vue
|
||||||
|
|
||||||
return (_openBlock(false), _createBlock(_Fragment, null, _renderList(list, (i) => {
|
return (_openBlock(true), _createBlock(_Fragment, null, _renderList(list, (i) => {
|
||||||
return (_openBlock(), _createBlock(_Fragment, null, [
|
return (_openBlock(), _createBlock(_Fragment, null, [
|
||||||
_createTextVNode(\\"foo\\")
|
_createTextVNode(\\"foo\\")
|
||||||
], 64 /* STABLE_FRAGMENT */))
|
], 64 /* STABLE_FRAGMENT */))
|
||||||
@ -19,11 +19,11 @@ return function render() {
|
|||||||
exports[`compiler: transform text consecutive text 1`] = `
|
exports[`compiler: transform text consecutive text 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { toString: _toString } = _Vue
|
const { toDisplayString: _toDisplayString } = _Vue
|
||||||
|
|
||||||
return _toString(foo) + \\" bar \\" + _toString(baz)
|
return _toDisplayString(foo) + \\" bar \\" + _toDisplayString(baz)
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@ -31,13 +31,13 @@ return function render() {
|
|||||||
exports[`compiler: transform text consecutive text between elements 1`] = `
|
exports[`compiler: transform text consecutive text between elements 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { createVNode: _createVNode, toString: _toString, createTextVNode: _createTextVNode, createBlock: _createBlock, Fragment: _Fragment, openBlock: _openBlock } = _Vue
|
const { createVNode: _createVNode, toDisplayString: _toDisplayString, createTextVNode: _createTextVNode, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||||
|
|
||||||
return (_openBlock(), _createBlock(_Fragment, null, [
|
return (_openBlock(), _createBlock(_Fragment, null, [
|
||||||
_createVNode(\\"div\\"),
|
_createVNode(\\"div\\"),
|
||||||
_createTextVNode(_toString(foo) + \\" bar \\" + _toString(baz), 1 /* TEXT */),
|
_createTextVNode(_toDisplayString(foo) + \\" bar \\" + _toDisplayString(baz), 1 /* TEXT */),
|
||||||
_createVNode(\\"div\\")
|
_createVNode(\\"div\\")
|
||||||
], 64 /* STABLE_FRAGMENT */))
|
], 64 /* STABLE_FRAGMENT */))
|
||||||
}
|
}
|
||||||
@ -47,13 +47,13 @@ return function render() {
|
|||||||
exports[`compiler: transform text consecutive text mixed with elements 1`] = `
|
exports[`compiler: transform text consecutive text mixed with elements 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { createVNode: _createVNode, toString: _toString, createTextVNode: _createTextVNode, createBlock: _createBlock, Fragment: _Fragment, openBlock: _openBlock } = _Vue
|
const { createVNode: _createVNode, toDisplayString: _toDisplayString, createTextVNode: _createTextVNode, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||||
|
|
||||||
return (_openBlock(), _createBlock(_Fragment, null, [
|
return (_openBlock(), _createBlock(_Fragment, null, [
|
||||||
_createVNode(\\"div\\"),
|
_createVNode(\\"div\\"),
|
||||||
_createTextVNode(_toString(foo) + \\" bar \\" + _toString(baz), 1 /* TEXT */),
|
_createTextVNode(_toDisplayString(foo) + \\" bar \\" + _toDisplayString(baz), 1 /* TEXT */),
|
||||||
_createVNode(\\"div\\"),
|
_createVNode(\\"div\\"),
|
||||||
_createTextVNode(\\"hello\\"),
|
_createTextVNode(\\"hello\\"),
|
||||||
_createVNode(\\"div\\")
|
_createVNode(\\"div\\")
|
||||||
@ -65,11 +65,11 @@ return function render() {
|
|||||||
exports[`compiler: transform text no consecutive text 1`] = `
|
exports[`compiler: transform text no consecutive text 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { toString: _toString } = _Vue
|
const { toDisplayString: _toDisplayString } = _Vue
|
||||||
|
|
||||||
return _toString(foo)
|
return _toDisplayString(foo)
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@ -77,9 +77,9 @@ return function render() {
|
|||||||
exports[`compiler: transform text text between elements (static) 1`] = `
|
exports[`compiler: transform text text between elements (static) 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { createVNode: _createVNode, createTextVNode: _createTextVNode, createBlock: _createBlock, Fragment: _Fragment, openBlock: _openBlock } = _Vue
|
const { createVNode: _createVNode, createTextVNode: _createTextVNode, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||||
|
|
||||||
return (_openBlock(), _createBlock(_Fragment, null, [
|
return (_openBlock(), _createBlock(_Fragment, null, [
|
||||||
_createVNode(\\"div\\"),
|
_createVNode(\\"div\\"),
|
||||||
@ -91,10 +91,9 @@ return function render() {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`compiler: transform text with prefixIdentifiers: true 1`] = `
|
exports[`compiler: transform text with prefixIdentifiers: true 1`] = `
|
||||||
"const { toString } = Vue
|
"const { toDisplayString: _toDisplayString } = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
const _ctx = this
|
return _toDisplayString(_ctx.foo) + \\" bar \\" + _toDisplayString(_ctx.baz + _ctx.qux)
|
||||||
return toString(_ctx.foo) + \\" bar \\" + toString(_ctx.baz + _ctx.qux)
|
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
@ -3,11 +3,11 @@
|
|||||||
exports[`compiler: v-for codegen basic v-for 1`] = `
|
exports[`compiler: v-for codegen basic v-for 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { renderList: _renderList, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, createVNode: _createVNode } = _Vue
|
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, createVNode: _createVNode } = _Vue
|
||||||
|
|
||||||
return (_openBlock(false), _createBlock(_Fragment, null, _renderList(items, (item) => {
|
return (_openBlock(true), _createBlock(_Fragment, null, _renderList(items, (item) => {
|
||||||
return (_openBlock(), _createBlock(\\"span\\"))
|
return (_openBlock(), _createBlock(\\"span\\"))
|
||||||
}), 256 /* UNKEYED_FRAGMENT */))
|
}), 256 /* UNKEYED_FRAGMENT */))
|
||||||
}
|
}
|
||||||
@ -17,11 +17,11 @@ return function render() {
|
|||||||
exports[`compiler: v-for codegen keyed template v-for 1`] = `
|
exports[`compiler: v-for codegen keyed template v-for 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { renderList: _renderList, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, createVNode: _createVNode } = _Vue
|
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, createVNode: _createVNode } = _Vue
|
||||||
|
|
||||||
return (_openBlock(false), _createBlock(_Fragment, null, _renderList(items, (item) => {
|
return (_openBlock(true), _createBlock(_Fragment, null, _renderList(items, (item) => {
|
||||||
return (_openBlock(), _createBlock(_Fragment, { key: item }, [
|
return (_openBlock(), _createBlock(_Fragment, { key: item }, [
|
||||||
\\"hello\\",
|
\\"hello\\",
|
||||||
_createVNode(\\"span\\")
|
_createVNode(\\"span\\")
|
||||||
@ -34,11 +34,11 @@ return function render() {
|
|||||||
exports[`compiler: v-for codegen keyed v-for 1`] = `
|
exports[`compiler: v-for codegen keyed v-for 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { renderList: _renderList, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, createVNode: _createVNode } = _Vue
|
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||||
|
|
||||||
return (_openBlock(false), _createBlock(_Fragment, null, _renderList(items, (item) => {
|
return (_openBlock(true), _createBlock(_Fragment, null, _renderList(items, (item) => {
|
||||||
return (_openBlock(), _createBlock(\\"span\\", { key: item }))
|
return (_openBlock(), _createBlock(\\"span\\", { key: item }))
|
||||||
}), 128 /* KEYED_FRAGMENT */))
|
}), 128 /* KEYED_FRAGMENT */))
|
||||||
}
|
}
|
||||||
@ -48,11 +48,11 @@ return function render() {
|
|||||||
exports[`compiler: v-for codegen skipped key 1`] = `
|
exports[`compiler: v-for codegen skipped key 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { renderList: _renderList, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, createVNode: _createVNode } = _Vue
|
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, createVNode: _createVNode } = _Vue
|
||||||
|
|
||||||
return (_openBlock(false), _createBlock(_Fragment, null, _renderList(items, (item, __, index) => {
|
return (_openBlock(true), _createBlock(_Fragment, null, _renderList(items, (item, __, index) => {
|
||||||
return (_openBlock(), _createBlock(\\"span\\"))
|
return (_openBlock(), _createBlock(\\"span\\"))
|
||||||
}), 256 /* UNKEYED_FRAGMENT */))
|
}), 256 /* UNKEYED_FRAGMENT */))
|
||||||
}
|
}
|
||||||
@ -62,11 +62,11 @@ return function render() {
|
|||||||
exports[`compiler: v-for codegen skipped value & key 1`] = `
|
exports[`compiler: v-for codegen skipped value & key 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { renderList: _renderList, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, createVNode: _createVNode } = _Vue
|
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, createVNode: _createVNode } = _Vue
|
||||||
|
|
||||||
return (_openBlock(false), _createBlock(_Fragment, null, _renderList(items, (_, __, index) => {
|
return (_openBlock(true), _createBlock(_Fragment, null, _renderList(items, (_, __, index) => {
|
||||||
return (_openBlock(), _createBlock(\\"span\\"))
|
return (_openBlock(), _createBlock(\\"span\\"))
|
||||||
}), 256 /* UNKEYED_FRAGMENT */))
|
}), 256 /* UNKEYED_FRAGMENT */))
|
||||||
}
|
}
|
||||||
@ -76,11 +76,11 @@ return function render() {
|
|||||||
exports[`compiler: v-for codegen skipped value 1`] = `
|
exports[`compiler: v-for codegen skipped value 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { renderList: _renderList, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, createVNode: _createVNode } = _Vue
|
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, createVNode: _createVNode } = _Vue
|
||||||
|
|
||||||
return (_openBlock(false), _createBlock(_Fragment, null, _renderList(items, (_, key, index) => {
|
return (_openBlock(true), _createBlock(_Fragment, null, _renderList(items, (_, key, index) => {
|
||||||
return (_openBlock(), _createBlock(\\"span\\"))
|
return (_openBlock(), _createBlock(\\"span\\"))
|
||||||
}), 256 /* UNKEYED_FRAGMENT */))
|
}), 256 /* UNKEYED_FRAGMENT */))
|
||||||
}
|
}
|
||||||
@ -90,11 +90,11 @@ return function render() {
|
|||||||
exports[`compiler: v-for codegen template v-for 1`] = `
|
exports[`compiler: v-for codegen template v-for 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { renderList: _renderList, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, createVNode: _createVNode } = _Vue
|
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, createVNode: _createVNode } = _Vue
|
||||||
|
|
||||||
return (_openBlock(false), _createBlock(_Fragment, null, _renderList(items, (item) => {
|
return (_openBlock(true), _createBlock(_Fragment, null, _renderList(items, (item) => {
|
||||||
return (_openBlock(), _createBlock(_Fragment, null, [
|
return (_openBlock(), _createBlock(_Fragment, null, [
|
||||||
\\"hello\\",
|
\\"hello\\",
|
||||||
_createVNode(\\"span\\")
|
_createVNode(\\"span\\")
|
||||||
@ -107,11 +107,11 @@ return function render() {
|
|||||||
exports[`compiler: v-for codegen template v-for w/ <slot/> 1`] = `
|
exports[`compiler: v-for codegen template v-for w/ <slot/> 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { renderList: _renderList, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, renderSlot: _renderSlot } = _Vue
|
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, renderSlot: _renderSlot } = _Vue
|
||||||
|
|
||||||
return (_openBlock(false), _createBlock(_Fragment, null, _renderList(items, (item) => {
|
return (_openBlock(true), _createBlock(_Fragment, null, _renderList(items, (item) => {
|
||||||
return _renderSlot($slots, \\"default\\")
|
return _renderSlot($slots, \\"default\\")
|
||||||
}), 256 /* UNKEYED_FRAGMENT */))
|
}), 256 /* UNKEYED_FRAGMENT */))
|
||||||
}
|
}
|
||||||
@ -121,11 +121,11 @@ return function render() {
|
|||||||
exports[`compiler: v-for codegen v-for on <slot/> 1`] = `
|
exports[`compiler: v-for codegen v-for on <slot/> 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { renderList: _renderList, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, renderSlot: _renderSlot } = _Vue
|
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, renderSlot: _renderSlot } = _Vue
|
||||||
|
|
||||||
return (_openBlock(false), _createBlock(_Fragment, null, _renderList(items, (item) => {
|
return (_openBlock(true), _createBlock(_Fragment, null, _renderList(items, (item) => {
|
||||||
return _renderSlot($slots, \\"default\\")
|
return _renderSlot($slots, \\"default\\")
|
||||||
}), 256 /* UNKEYED_FRAGMENT */))
|
}), 256 /* UNKEYED_FRAGMENT */))
|
||||||
}
|
}
|
||||||
@ -135,16 +135,16 @@ return function render() {
|
|||||||
exports[`compiler: v-for codegen v-for on element with custom directive 1`] = `
|
exports[`compiler: v-for codegen v-for on element with custom directive 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { renderList: _renderList, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, createVNode: _createVNode, withDirectives: _withDirectives, resolveDirective: _resolveDirective } = _Vue
|
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, resolveDirective: _resolveDirective, createVNode: _createVNode, withDirectives: _withDirectives } = _Vue
|
||||||
|
|
||||||
const _directive_foo = _resolveDirective(\\"foo\\")
|
const _directive_foo = _resolveDirective(\\"foo\\")
|
||||||
|
|
||||||
return (_openBlock(false), _createBlock(_Fragment, null, _renderList(list, (i) => {
|
return (_openBlock(true), _createBlock(_Fragment, null, _renderList(list, (i) => {
|
||||||
return (_openBlock(), _withDirectives(_createBlock(\\"div\\", null, null, 32 /* NEED_PATCH */), [
|
return _withDirectives((_openBlock(), _createBlock(\\"div\\", null, null, 512 /* NEED_PATCH */)), [
|
||||||
[_directive_foo]
|
[_directive_foo]
|
||||||
]))
|
])
|
||||||
}), 256 /* UNKEYED_FRAGMENT */))
|
}), 256 /* UNKEYED_FRAGMENT */))
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@ -153,15 +153,15 @@ return function render() {
|
|||||||
exports[`compiler: v-for codegen v-if + v-for 1`] = `
|
exports[`compiler: v-for codegen v-if + v-for 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { openBlock: _openBlock, renderList: _renderList, createBlock: _createBlock, Fragment: _Fragment, createVNode: _createVNode, createCommentVNode: _createCommentVNode } = _Vue
|
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, createVNode: _createVNode, createCommentVNode: _createCommentVNode } = _Vue
|
||||||
|
|
||||||
return (_openBlock(), ok
|
return ok
|
||||||
? _createBlock(_Fragment, { key: 0 }, _renderList(list, (i) => {
|
? (_openBlock(true), _createBlock(_Fragment, { key: 0 }, _renderList(list, (i) => {
|
||||||
return (_openBlock(), _createBlock(\\"div\\"))
|
return (_openBlock(), _createBlock(\\"div\\"))
|
||||||
}), 256 /* UNKEYED_FRAGMENT */)
|
}), 256 /* UNKEYED_FRAGMENT */))
|
||||||
: _createCommentVNode(\\"v-if\\", true))
|
: _createCommentVNode(\\"v-if\\", true)
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@ -169,11 +169,11 @@ return function render() {
|
|||||||
exports[`compiler: v-for codegen value + key + index 1`] = `
|
exports[`compiler: v-for codegen value + key + index 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { renderList: _renderList, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, createVNode: _createVNode } = _Vue
|
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, createVNode: _createVNode } = _Vue
|
||||||
|
|
||||||
return (_openBlock(false), _createBlock(_Fragment, null, _renderList(items, (item, key, index) => {
|
return (_openBlock(true), _createBlock(_Fragment, null, _renderList(items, (item, key, index) => {
|
||||||
return (_openBlock(), _createBlock(\\"span\\"))
|
return (_openBlock(), _createBlock(\\"span\\"))
|
||||||
}), 256 /* UNKEYED_FRAGMENT */))
|
}), 256 /* UNKEYED_FRAGMENT */))
|
||||||
}
|
}
|
||||||
|
@ -3,13 +3,13 @@
|
|||||||
exports[`compiler: v-if codegen basic v-if 1`] = `
|
exports[`compiler: v-if codegen basic v-if 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, createCommentVNode: _createCommentVNode } = _Vue
|
const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock, createCommentVNode: _createCommentVNode } = _Vue
|
||||||
|
|
||||||
return (_openBlock(), ok
|
return ok
|
||||||
? _createBlock(\\"div\\", { key: 0 })
|
? (_openBlock(), _createBlock(\\"div\\", { key: 0 }))
|
||||||
: _createCommentVNode(\\"v-if\\", true))
|
: _createCommentVNode(\\"v-if\\", true)
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@ -17,17 +17,17 @@ return function render() {
|
|||||||
exports[`compiler: v-if codegen template v-if 1`] = `
|
exports[`compiler: v-if codegen template v-if 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { openBlock: _openBlock, createVNode: _createVNode, Fragment: _Fragment, createBlock: _createBlock, createCommentVNode: _createCommentVNode } = _Vue
|
const { createVNode: _createVNode, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, createCommentVNode: _createCommentVNode } = _Vue
|
||||||
|
|
||||||
return (_openBlock(), ok
|
return ok
|
||||||
? _createBlock(_Fragment, { key: 0 }, [
|
? (_openBlock(), _createBlock(_Fragment, { key: 0 }, [
|
||||||
_createVNode(\\"div\\"),
|
_createVNode(\\"div\\"),
|
||||||
\\"hello\\",
|
\\"hello\\",
|
||||||
_createVNode(\\"p\\")
|
_createVNode(\\"p\\")
|
||||||
])
|
], 64 /* STABLE_FRAGMENT */))
|
||||||
: _createCommentVNode(\\"v-if\\", true))
|
: _createCommentVNode(\\"v-if\\", true)
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@ -35,13 +35,13 @@ return function render() {
|
|||||||
exports[`compiler: v-if codegen template v-if w/ single <slot/> child 1`] = `
|
exports[`compiler: v-if codegen template v-if w/ single <slot/> child 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { openBlock: _openBlock, renderSlot: _renderSlot, createCommentVNode: _createCommentVNode } = _Vue
|
const { renderSlot: _renderSlot, createCommentVNode: _createCommentVNode } = _Vue
|
||||||
|
|
||||||
return (_openBlock(), ok
|
return ok
|
||||||
? _renderSlot($slots, \\"default\\", { key: 0 })
|
? _renderSlot($slots, \\"default\\", { key: 0 })
|
||||||
: _createCommentVNode(\\"v-if\\", true))
|
: _createCommentVNode(\\"v-if\\", true)
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@ -49,13 +49,13 @@ return function render() {
|
|||||||
exports[`compiler: v-if codegen v-if + v-else 1`] = `
|
exports[`compiler: v-if codegen v-if + v-else 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, createCommentVNode: _createCommentVNode } = _Vue
|
const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock, createCommentVNode: _createCommentVNode } = _Vue
|
||||||
|
|
||||||
return (_openBlock(), ok
|
return ok
|
||||||
? _createBlock(\\"div\\", { key: 0 })
|
? (_openBlock(), _createBlock(\\"div\\", { key: 0 }))
|
||||||
: _createBlock(\\"p\\", { key: 1 }))
|
: (_openBlock(), _createBlock(\\"p\\", { key: 1 }))
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@ -63,15 +63,15 @@ return function render() {
|
|||||||
exports[`compiler: v-if codegen v-if + v-else-if + v-else 1`] = `
|
exports[`compiler: v-if codegen v-if + v-else-if + v-else 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, createCommentVNode: _createCommentVNode, Fragment: _Fragment } = _Vue
|
const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock, createCommentVNode: _createCommentVNode, Fragment: _Fragment } = _Vue
|
||||||
|
|
||||||
return (_openBlock(), ok
|
return ok
|
||||||
? _createBlock(\\"div\\", { key: 0 })
|
? (_openBlock(), _createBlock(\\"div\\", { key: 0 }))
|
||||||
: orNot
|
: orNot
|
||||||
? _createBlock(\\"p\\", { key: 1 })
|
? (_openBlock(), _createBlock(\\"p\\", { key: 1 }))
|
||||||
: _createBlock(_Fragment, { key: 2 }, [\\"fine\\"]))
|
: (_openBlock(), _createBlock(_Fragment, { key: 2 }, [\\"fine\\"], 64 /* STABLE_FRAGMENT */))
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@ -79,15 +79,15 @@ return function render() {
|
|||||||
exports[`compiler: v-if codegen v-if + v-else-if 1`] = `
|
exports[`compiler: v-if codegen v-if + v-else-if 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, createCommentVNode: _createCommentVNode } = _Vue
|
const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock, createCommentVNode: _createCommentVNode } = _Vue
|
||||||
|
|
||||||
return (_openBlock(), ok
|
return ok
|
||||||
? _createBlock(\\"div\\", { key: 0 })
|
? (_openBlock(), _createBlock(\\"div\\", { key: 0 }))
|
||||||
: orNot
|
: orNot
|
||||||
? _createBlock(\\"p\\", { key: 1 })
|
? (_openBlock(), _createBlock(\\"p\\", { key: 1 }))
|
||||||
: _createCommentVNode(\\"v-if\\", true))
|
: _createCommentVNode(\\"v-if\\", true)
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@ -95,13 +95,27 @@ return function render() {
|
|||||||
exports[`compiler: v-if codegen v-if on <slot/> 1`] = `
|
exports[`compiler: v-if codegen v-if on <slot/> 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { openBlock: _openBlock, renderSlot: _renderSlot, createCommentVNode: _createCommentVNode } = _Vue
|
const { renderSlot: _renderSlot, createCommentVNode: _createCommentVNode } = _Vue
|
||||||
|
|
||||||
return (_openBlock(), ok
|
return ok
|
||||||
? _renderSlot($slots, \\"default\\", { key: 0 })
|
? _renderSlot($slots, \\"default\\", { key: 0 })
|
||||||
: _createCommentVNode(\\"v-if\\", true))
|
: _createCommentVNode(\\"v-if\\", true)
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`compiler: v-if codegen v-if with key 1`] = `
|
||||||
|
"const _Vue = Vue
|
||||||
|
|
||||||
|
return function render(_ctx, _cache) {
|
||||||
|
with (_ctx) {
|
||||||
|
const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock, createCommentVNode: _createCommentVNode } = _Vue
|
||||||
|
|
||||||
|
return ok
|
||||||
|
? (_openBlock(), _createBlock(\\"div\\", { key: \\"some-key\\" }))
|
||||||
|
: _createCommentVNode(\\"v-if\\", true)
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`compiler: transform v-model compound expression (with prefixIdentifiers) 1`] = `
|
exports[`compiler: transform v-model compound expression (with prefixIdentifiers) 1`] = `
|
||||||
"import { createVNode, createBlock, openBlock } from \\"vue\\"
|
"import { createVNode as _createVNode, openBlock as _openBlock, createBlock as _createBlock } from \\"vue\\"
|
||||||
|
|
||||||
export default function render() {
|
export function render(_ctx, _cache) {
|
||||||
const _ctx = this
|
return (_openBlock(), _createBlock(\\"input\\", {
|
||||||
return (openBlock(), createBlock(\\"input\\", {
|
|
||||||
modelValue: _ctx.model[_ctx.index],
|
modelValue: _ctx.model[_ctx.index],
|
||||||
\\"onUpdate:modelValue\\": $event => (_ctx.model[_ctx.index] = $event)
|
\\"onUpdate:modelValue\\": $event => (_ctx.model[_ctx.index] = $event)
|
||||||
}, null, 8 /* PROPS */, [\\"modelValue\\", \\"onUpdate:modelValue\\"]))
|
}, null, 8 /* PROPS */, [\\"modelValue\\", \\"onUpdate:modelValue\\"]))
|
||||||
@ -15,9 +14,9 @@ export default function render() {
|
|||||||
exports[`compiler: transform v-model compound expression 1`] = `
|
exports[`compiler: transform v-model compound expression 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||||
|
|
||||||
return (_openBlock(), _createBlock(\\"input\\", {
|
return (_openBlock(), _createBlock(\\"input\\", {
|
||||||
modelValue: model[index],
|
modelValue: model[index],
|
||||||
@ -28,11 +27,10 @@ return function render() {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`compiler: transform v-model simple exprssion (with prefixIdentifiers) 1`] = `
|
exports[`compiler: transform v-model simple exprssion (with prefixIdentifiers) 1`] = `
|
||||||
"import { createVNode, createBlock, openBlock } from \\"vue\\"
|
"import { createVNode as _createVNode, openBlock as _openBlock, createBlock as _createBlock } from \\"vue\\"
|
||||||
|
|
||||||
export default function render() {
|
export function render(_ctx, _cache) {
|
||||||
const _ctx = this
|
return (_openBlock(), _createBlock(\\"input\\", {
|
||||||
return (openBlock(), createBlock(\\"input\\", {
|
|
||||||
modelValue: _ctx.model,
|
modelValue: _ctx.model,
|
||||||
\\"onUpdate:modelValue\\": $event => (_ctx.model = $event)
|
\\"onUpdate:modelValue\\": $event => (_ctx.model = $event)
|
||||||
}, null, 8 /* PROPS */, [\\"modelValue\\", \\"onUpdate:modelValue\\"]))
|
}, null, 8 /* PROPS */, [\\"modelValue\\", \\"onUpdate:modelValue\\"]))
|
||||||
@ -42,9 +40,9 @@ export default function render() {
|
|||||||
exports[`compiler: transform v-model simple exprssion 1`] = `
|
exports[`compiler: transform v-model simple exprssion 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||||
|
|
||||||
return (_openBlock(), _createBlock(\\"input\\", {
|
return (_openBlock(), _createBlock(\\"input\\", {
|
||||||
modelValue: model,
|
modelValue: model,
|
||||||
@ -57,24 +55,23 @@ return function render() {
|
|||||||
exports[`compiler: transform v-model with argument 1`] = `
|
exports[`compiler: transform v-model with argument 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||||
|
|
||||||
return (_openBlock(), _createBlock(\\"input\\", {
|
return (_openBlock(), _createBlock(\\"input\\", {
|
||||||
value: model,
|
value: model,
|
||||||
\\"onUpdate:value\\": $event => (model = $event)
|
\\"onUpdate:value\\": $event => (model = $event)
|
||||||
}, null, 8 /* PROPS */, [\\"value\\", \\"onUpdate:value\\"]))
|
}, null, 40 /* PROPS, HYDRATE_EVENTS */, [\\"value\\", \\"onUpdate:value\\"]))
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`compiler: transform v-model with dynamic argument (with prefixIdentifiers) 1`] = `
|
exports[`compiler: transform v-model with dynamic argument (with prefixIdentifiers) 1`] = `
|
||||||
"import { createVNode, createBlock, openBlock } from \\"vue\\"
|
"import { createVNode as _createVNode, openBlock as _openBlock, createBlock as _createBlock } from \\"vue\\"
|
||||||
|
|
||||||
export default function render() {
|
export function render(_ctx, _cache) {
|
||||||
const _ctx = this
|
return (_openBlock(), _createBlock(\\"input\\", {
|
||||||
return (openBlock(), createBlock(\\"input\\", {
|
|
||||||
[_ctx.value]: _ctx.model,
|
[_ctx.value]: _ctx.model,
|
||||||
[\\"onUpdate:\\" + _ctx.value]: $event => (_ctx.model = $event)
|
[\\"onUpdate:\\" + _ctx.value]: $event => (_ctx.model = $event)
|
||||||
}, null, 16 /* FULL_PROPS */))
|
}, null, 16 /* FULL_PROPS */))
|
||||||
@ -84,9 +81,9 @@ export default function render() {
|
|||||||
exports[`compiler: transform v-model with dynamic argument 1`] = `
|
exports[`compiler: transform v-model with dynamic argument 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||||
|
|
||||||
return (_openBlock(), _createBlock(\\"input\\", {
|
return (_openBlock(), _createBlock(\\"input\\", {
|
||||||
[value]: model,
|
[value]: model,
|
||||||
|
@ -3,10 +3,9 @@
|
|||||||
exports[`compiler: v-once transform as root node 1`] = `
|
exports[`compiler: v-once transform as root node 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { setBlockTracking: _setBlockTracking, createVNode: _createVNode } = _Vue
|
const { setBlockTracking: _setBlockTracking, createVNode: _createVNode } = _Vue
|
||||||
const _cache = $cache
|
|
||||||
|
|
||||||
return _cache[1] || (
|
return _cache[1] || (
|
||||||
_setBlockTracking(-1),
|
_setBlockTracking(-1),
|
||||||
@ -21,10 +20,9 @@ return function render() {
|
|||||||
exports[`compiler: v-once transform on component 1`] = `
|
exports[`compiler: v-once transform on component 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { setBlockTracking: _setBlockTracking, resolveComponent: _resolveComponent, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
const { setBlockTracking: _setBlockTracking, resolveComponent: _resolveComponent, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||||
const _cache = $cache
|
|
||||||
|
|
||||||
const _component_Comp = _resolveComponent(\\"Comp\\")
|
const _component_Comp = _resolveComponent(\\"Comp\\")
|
||||||
|
|
||||||
@ -43,10 +41,9 @@ return function render() {
|
|||||||
exports[`compiler: v-once transform on nested plain element 1`] = `
|
exports[`compiler: v-once transform on nested plain element 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { setBlockTracking: _setBlockTracking, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
const { setBlockTracking: _setBlockTracking, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||||
const _cache = $cache
|
|
||||||
|
|
||||||
return (_openBlock(), _createBlock(\\"div\\", null, [
|
return (_openBlock(), _createBlock(\\"div\\", null, [
|
||||||
_cache[1] || (
|
_cache[1] || (
|
||||||
@ -63,10 +60,9 @@ return function render() {
|
|||||||
exports[`compiler: v-once transform on slot outlet 1`] = `
|
exports[`compiler: v-once transform on slot outlet 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { setBlockTracking: _setBlockTracking, renderSlot: _renderSlot, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
const { setBlockTracking: _setBlockTracking, renderSlot: _renderSlot, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||||
const _cache = $cache
|
|
||||||
|
|
||||||
return (_openBlock(), _createBlock(\\"div\\", null, [
|
return (_openBlock(), _createBlock(\\"div\\", null, [
|
||||||
_cache[1] || (
|
_cache[1] || (
|
||||||
@ -83,10 +79,9 @@ return function render() {
|
|||||||
exports[`compiler: v-once transform with hoistStatic: true 1`] = `
|
exports[`compiler: v-once transform with hoistStatic: true 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { setBlockTracking: _setBlockTracking, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
const { setBlockTracking: _setBlockTracking, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||||
const _cache = $cache
|
|
||||||
|
|
||||||
return (_openBlock(), _createBlock(\\"div\\", null, [
|
return (_openBlock(), _createBlock(\\"div\\", null, [
|
||||||
_cache[1] || (
|
_cache[1] || (
|
||||||
|
@ -1,111 +1,93 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`compiler: transform component slots dynamically named slots 1`] = `
|
exports[`compiler: transform component slots dynamically named slots 1`] = `
|
||||||
"const { toString, resolveComponent, createVNode, createBlock, openBlock } = Vue
|
"const { toDisplayString: _toDisplayString, resolveComponent: _resolveComponent, withCtx: _withCtx, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
const _ctx = this
|
const _component_Comp = _resolveComponent(\\"Comp\\")
|
||||||
const _component_Comp = resolveComponent(\\"Comp\\")
|
|
||||||
|
|
||||||
return (openBlock(), createBlock(_component_Comp, null, {
|
return (_openBlock(), _createBlock(_component_Comp, null, {
|
||||||
[_ctx.one]: ({ foo }) => [toString(foo), toString(_ctx.bar)],
|
[_ctx.one]: _withCtx(({ foo }) => [_toDisplayString(foo), _toDisplayString(_ctx.bar)]),
|
||||||
[_ctx.two]: ({ bar }) => [toString(_ctx.foo), toString(bar)],
|
[_ctx.two]: _withCtx(({ bar }) => [_toDisplayString(_ctx.foo), _toDisplayString(bar)]),
|
||||||
_compiled: true
|
_: 1
|
||||||
}, 512 /* DYNAMIC_SLOTS */))
|
}, 1024 /* DYNAMIC_SLOTS */))
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`compiler: transform component slots explicit default slot 1`] = `
|
|
||||||
"const { toString, resolveComponent, createVNode, createBlock, openBlock } = Vue
|
|
||||||
|
|
||||||
return function render() {
|
|
||||||
const _ctx = this
|
|
||||||
const _component_Comp = resolveComponent(\\"Comp\\")
|
|
||||||
|
|
||||||
return (openBlock(), createBlock(_component_Comp, null, {
|
|
||||||
default: ({ foo }) => [toString(foo), toString(_ctx.bar)],
|
|
||||||
_compiled: true
|
|
||||||
}))
|
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`compiler: transform component slots implicit default slot 1`] = `
|
exports[`compiler: transform component slots implicit default slot 1`] = `
|
||||||
"const { createVNode, resolveComponent, createBlock, openBlock } = Vue
|
"const { createVNode: _createVNode, resolveComponent: _resolveComponent, withCtx: _withCtx, openBlock: _openBlock, createBlock: _createBlock } = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
const _ctx = this
|
const _component_Comp = _resolveComponent(\\"Comp\\")
|
||||||
const _component_Comp = resolveComponent(\\"Comp\\")
|
|
||||||
|
|
||||||
return (openBlock(), createBlock(_component_Comp, null, {
|
return (_openBlock(), _createBlock(_component_Comp, null, {
|
||||||
default: () => [
|
default: _withCtx(() => [
|
||||||
createVNode(\\"div\\")
|
_createVNode(\\"div\\")
|
||||||
],
|
]),
|
||||||
_compiled: true
|
_: 1
|
||||||
}))
|
}))
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`compiler: transform component slots named slot with v-for w/ prefixIdentifiers: true 1`] = `
|
exports[`compiler: transform component slots named slot with v-for w/ prefixIdentifiers: true 1`] = `
|
||||||
"const { toString, resolveComponent, renderList, createSlots, createVNode, createBlock, openBlock } = Vue
|
"const { toDisplayString: _toDisplayString, resolveComponent: _resolveComponent, withCtx: _withCtx, renderList: _renderList, createSlots: _createSlots, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
const _ctx = this
|
const _component_Comp = _resolveComponent(\\"Comp\\")
|
||||||
const _component_Comp = resolveComponent(\\"Comp\\")
|
|
||||||
|
|
||||||
return (openBlock(), createBlock(_component_Comp, null, createSlots({ _compiled: true }, [
|
return (_openBlock(), _createBlock(_component_Comp, null, _createSlots({ _: 1 }, [
|
||||||
renderList(_ctx.list, (name) => {
|
_renderList(_ctx.list, (name) => {
|
||||||
return {
|
return {
|
||||||
name: name,
|
name: name,
|
||||||
fn: () => [toString(name)]
|
fn: _withCtx(() => [_toDisplayString(name)])
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
]), 512 /* DYNAMIC_SLOTS */))
|
]), 1024 /* DYNAMIC_SLOTS */))
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`compiler: transform component slots named slot with v-if + prefixIdentifiers: true 1`] = `
|
exports[`compiler: transform component slots named slot with v-if + prefixIdentifiers: true 1`] = `
|
||||||
"const { toString, resolveComponent, createSlots, createVNode, createBlock, openBlock } = Vue
|
"const { toDisplayString: _toDisplayString, resolveComponent: _resolveComponent, withCtx: _withCtx, createSlots: _createSlots, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
const _ctx = this
|
const _component_Comp = _resolveComponent(\\"Comp\\")
|
||||||
const _component_Comp = resolveComponent(\\"Comp\\")
|
|
||||||
|
|
||||||
return (openBlock(), createBlock(_component_Comp, null, createSlots({ _compiled: true }, [
|
return (_openBlock(), _createBlock(_component_Comp, null, _createSlots({ _: 1 }, [
|
||||||
(_ctx.ok)
|
(_ctx.ok)
|
||||||
? {
|
? {
|
||||||
name: \\"one\\",
|
name: \\"one\\",
|
||||||
fn: (props) => [toString(props)]
|
fn: _withCtx((props) => [_toDisplayString(props)])
|
||||||
}
|
}
|
||||||
: undefined
|
: undefined
|
||||||
]), 512 /* DYNAMIC_SLOTS */))
|
]), 1024 /* DYNAMIC_SLOTS */))
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`compiler: transform component slots named slot with v-if + v-else-if + v-else 1`] = `
|
exports[`compiler: transform component slots named slot with v-if + v-else-if + v-else 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { resolveComponent: _resolveComponent, createSlots: _createSlots, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
const { resolveComponent: _resolveComponent, withCtx: _withCtx, createSlots: _createSlots, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||||
|
|
||||||
const _component_Comp = _resolveComponent(\\"Comp\\")
|
const _component_Comp = _resolveComponent(\\"Comp\\")
|
||||||
|
|
||||||
return (_openBlock(), _createBlock(_component_Comp, null, _createSlots({ _compiled: true }, [
|
return (_openBlock(), _createBlock(_component_Comp, null, _createSlots({ _: 1 }, [
|
||||||
ok
|
ok
|
||||||
? {
|
? {
|
||||||
name: \\"one\\",
|
name: \\"one\\",
|
||||||
fn: () => [\\"foo\\"]
|
fn: _withCtx(() => [\\"foo\\"])
|
||||||
}
|
}
|
||||||
: orNot
|
: orNot
|
||||||
? {
|
? {
|
||||||
name: \\"two\\",
|
name: \\"two\\",
|
||||||
fn: (props) => [\\"bar\\"]
|
fn: _withCtx((props) => [\\"bar\\"])
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
name: \\"one\\",
|
name: \\"one\\",
|
||||||
fn: () => [\\"baz\\"]
|
fn: _withCtx(() => [\\"baz\\"])
|
||||||
}
|
}
|
||||||
]), 512 /* DYNAMIC_SLOTS */))
|
]), 1024 /* DYNAMIC_SLOTS */))
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@ -113,59 +95,117 @@ return function render() {
|
|||||||
exports[`compiler: transform component slots named slot with v-if 1`] = `
|
exports[`compiler: transform component slots named slot with v-if 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { resolveComponent: _resolveComponent, createSlots: _createSlots, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
const { resolveComponent: _resolveComponent, withCtx: _withCtx, createSlots: _createSlots, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||||
|
|
||||||
const _component_Comp = _resolveComponent(\\"Comp\\")
|
const _component_Comp = _resolveComponent(\\"Comp\\")
|
||||||
|
|
||||||
return (_openBlock(), _createBlock(_component_Comp, null, _createSlots({ _compiled: true }, [
|
return (_openBlock(), _createBlock(_component_Comp, null, _createSlots({ _: 1 }, [
|
||||||
ok
|
ok
|
||||||
? {
|
? {
|
||||||
name: \\"one\\",
|
name: \\"one\\",
|
||||||
fn: () => [\\"hello\\"]
|
fn: _withCtx(() => [\\"hello\\"])
|
||||||
}
|
}
|
||||||
: undefined
|
: undefined
|
||||||
]), 512 /* DYNAMIC_SLOTS */))
|
]), 1024 /* DYNAMIC_SLOTS */))
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`compiler: transform component slots named slots 1`] = `
|
exports[`compiler: transform component slots named slots w/ implicit default slot 1`] = `
|
||||||
"const { toString, resolveComponent, createVNode, createBlock, openBlock } = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
const _ctx = this
|
with (_ctx) {
|
||||||
const _component_Comp = resolveComponent(\\"Comp\\")
|
const { createVNode: _createVNode, resolveComponent: _resolveComponent, withCtx: _withCtx, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||||
|
|
||||||
return (openBlock(), createBlock(_component_Comp, null, {
|
const _component_Comp = _resolveComponent(\\"Comp\\")
|
||||||
one: ({ foo }) => [toString(foo), toString(_ctx.bar)],
|
|
||||||
two: ({ bar }) => [toString(_ctx.foo), toString(bar)],
|
return (_openBlock(), _createBlock(_component_Comp, null, {
|
||||||
_compiled: true
|
one: _withCtx(() => [\\"foo\\"]),
|
||||||
}))
|
default: _withCtx(() => [
|
||||||
|
\\"bar\\",
|
||||||
|
_createVNode(\\"span\\")
|
||||||
|
]),
|
||||||
|
_: 1
|
||||||
|
}))
|
||||||
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`compiler: transform component slots nested slots scoping 1`] = `
|
exports[`compiler: transform component slots nested slots scoping 1`] = `
|
||||||
"const { toString, resolveComponent, createVNode, createBlock, openBlock } = Vue
|
"const { toDisplayString: _toDisplayString, resolveComponent: _resolveComponent, withCtx: _withCtx, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
const _ctx = this
|
const _component_Inner = _resolveComponent(\\"Inner\\")
|
||||||
const _component_Inner = resolveComponent(\\"Inner\\")
|
const _component_Comp = _resolveComponent(\\"Comp\\")
|
||||||
const _component_Comp = resolveComponent(\\"Comp\\")
|
|
||||||
|
|
||||||
return (openBlock(), createBlock(_component_Comp, null, {
|
return (_openBlock(), _createBlock(_component_Comp, null, {
|
||||||
default: ({ foo }) => [
|
default: _withCtx(({ foo }) => [
|
||||||
createVNode(_component_Inner, null, {
|
_createVNode(_component_Inner, null, {
|
||||||
default: ({ bar }) => [toString(foo), toString(bar), toString(_ctx.baz)],
|
default: _withCtx(({ bar }) => [_toDisplayString(foo), _toDisplayString(bar), _toDisplayString(_ctx.baz)]),
|
||||||
_compiled: true
|
_: 1
|
||||||
}, 512 /* DYNAMIC_SLOTS */),
|
}, 1024 /* DYNAMIC_SLOTS */),
|
||||||
\\" \\",
|
\\" \\",
|
||||||
toString(foo),
|
_toDisplayString(foo),
|
||||||
toString(_ctx.bar),
|
_toDisplayString(_ctx.bar),
|
||||||
toString(_ctx.baz)
|
_toDisplayString(_ctx.baz)
|
||||||
],
|
]),
|
||||||
_compiled: true
|
_: 1
|
||||||
|
}))
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`compiler: transform component slots on component dynamically named slot 1`] = `
|
||||||
|
"const { toDisplayString: _toDisplayString, resolveComponent: _resolveComponent, withCtx: _withCtx, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = Vue
|
||||||
|
|
||||||
|
return function render(_ctx, _cache) {
|
||||||
|
const _component_Comp = _resolveComponent(\\"Comp\\")
|
||||||
|
|
||||||
|
return (_openBlock(), _createBlock(_component_Comp, null, {
|
||||||
|
[_ctx.named]: _withCtx(({ foo }) => [_toDisplayString(foo), _toDisplayString(_ctx.bar)]),
|
||||||
|
_: 1
|
||||||
|
}))
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`compiler: transform component slots on component named slot 1`] = `
|
||||||
|
"const { toDisplayString: _toDisplayString, resolveComponent: _resolveComponent, withCtx: _withCtx, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = Vue
|
||||||
|
|
||||||
|
return function render(_ctx, _cache) {
|
||||||
|
const _component_Comp = _resolveComponent(\\"Comp\\")
|
||||||
|
|
||||||
|
return (_openBlock(), _createBlock(_component_Comp, null, {
|
||||||
|
named: _withCtx(({ foo }) => [_toDisplayString(foo), _toDisplayString(_ctx.bar)]),
|
||||||
|
_: 1
|
||||||
|
}))
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`compiler: transform component slots on-component default slot 1`] = `
|
||||||
|
"const { toDisplayString: _toDisplayString, resolveComponent: _resolveComponent, withCtx: _withCtx, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = Vue
|
||||||
|
|
||||||
|
return function render(_ctx, _cache) {
|
||||||
|
const _component_Comp = _resolveComponent(\\"Comp\\")
|
||||||
|
|
||||||
|
return (_openBlock(), _createBlock(_component_Comp, null, {
|
||||||
|
default: _withCtx(({ foo }) => [_toDisplayString(foo), _toDisplayString(_ctx.bar)]),
|
||||||
|
_: 1
|
||||||
|
}))
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`compiler: transform component slots template named slots 1`] = `
|
||||||
|
"const { toDisplayString: _toDisplayString, resolveComponent: _resolveComponent, withCtx: _withCtx, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = Vue
|
||||||
|
|
||||||
|
return function render(_ctx, _cache) {
|
||||||
|
const _component_Comp = _resolveComponent(\\"Comp\\")
|
||||||
|
|
||||||
|
return (_openBlock(), _createBlock(_component_Comp, null, {
|
||||||
|
one: _withCtx(({ foo }) => [_toDisplayString(foo), _toDisplayString(_ctx.bar)]),
|
||||||
|
two: _withCtx(({ bar }) => [_toDisplayString(_ctx.foo), _toDisplayString(bar)]),
|
||||||
|
_: 1
|
||||||
}))
|
}))
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
@ -1,18 +1,15 @@
|
|||||||
import {
|
import {
|
||||||
parse,
|
baseParse as parse,
|
||||||
transform,
|
transform,
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
generate,
|
generate,
|
||||||
CompilerOptions
|
CompilerOptions,
|
||||||
|
VNodeCall,
|
||||||
|
IfNode,
|
||||||
|
ElementNode,
|
||||||
|
ForNode
|
||||||
} from '../../src'
|
} from '../../src'
|
||||||
import {
|
import { FRAGMENT, RENDER_LIST, CREATE_TEXT } from '../../src/runtimeHelpers'
|
||||||
OPEN_BLOCK,
|
|
||||||
CREATE_BLOCK,
|
|
||||||
CREATE_VNODE,
|
|
||||||
WITH_DIRECTIVES,
|
|
||||||
FRAGMENT,
|
|
||||||
RENDER_LIST
|
|
||||||
} from '../../src/runtimeHelpers'
|
|
||||||
import { transformElement } from '../../src/transforms/transformElement'
|
import { transformElement } from '../../src/transforms/transformElement'
|
||||||
import { transformExpression } from '../../src/transforms/transformExpression'
|
import { transformExpression } from '../../src/transforms/transformExpression'
|
||||||
import { transformIf } from '../../src/transforms/vIf'
|
import { transformIf } from '../../src/transforms/vIf'
|
||||||
@ -20,6 +17,7 @@ import { transformFor } from '../../src/transforms/vFor'
|
|||||||
import { transformBind } from '../../src/transforms/vBind'
|
import { transformBind } from '../../src/transforms/vBind'
|
||||||
import { transformOn } from '../../src/transforms/vOn'
|
import { transformOn } from '../../src/transforms/vOn'
|
||||||
import { createObjectMatcher, genFlagText } from '../testUtils'
|
import { createObjectMatcher, genFlagText } from '../testUtils'
|
||||||
|
import { transformText } from '../../src/transforms/transformText'
|
||||||
import { PatchFlags } from '@vue/shared'
|
import { PatchFlags } from '@vue/shared'
|
||||||
|
|
||||||
function transformWithHoist(template: string, options: CompilerOptions = {}) {
|
function transformWithHoist(template: string, options: CompilerOptions = {}) {
|
||||||
@ -30,7 +28,8 @@ function transformWithHoist(template: string, options: CompilerOptions = {}) {
|
|||||||
transformIf,
|
transformIf,
|
||||||
transformFor,
|
transformFor,
|
||||||
...(options.prefixIdentifiers ? [transformExpression] : []),
|
...(options.prefixIdentifiers ? [transformExpression] : []),
|
||||||
transformElement
|
transformElement,
|
||||||
|
transformText
|
||||||
],
|
],
|
||||||
directiveTransforms: {
|
directiveTransforms: {
|
||||||
on: transformOn,
|
on: transformOn,
|
||||||
@ -39,56 +38,43 @@ function transformWithHoist(template: string, options: CompilerOptions = {}) {
|
|||||||
...options
|
...options
|
||||||
})
|
})
|
||||||
expect(ast.codegenNode).toMatchObject({
|
expect(ast.codegenNode).toMatchObject({
|
||||||
type: NodeTypes.JS_SEQUENCE_EXPRESSION,
|
type: NodeTypes.VNODE_CALL,
|
||||||
expressions: [
|
isBlock: true
|
||||||
{
|
|
||||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
|
||||||
callee: OPEN_BLOCK
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
|
||||||
callee: CREATE_BLOCK
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
})
|
||||||
return {
|
return ast
|
||||||
root: ast,
|
|
||||||
args: (ast.codegenNode as any).expressions[1].arguments
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('compiler: hoistStatic transform', () => {
|
describe('compiler: hoistStatic transform', () => {
|
||||||
test('should NOT hoist root node', () => {
|
test('should NOT hoist root node', () => {
|
||||||
// if the whole tree is static, the root still needs to be a block
|
// if the whole tree is static, the root still needs to be a block
|
||||||
// so that it's patched in optimized mode to skip children
|
// so that it's patched in optimized mode to skip children
|
||||||
const { root, args } = transformWithHoist(`<div/>`)
|
const root = transformWithHoist(`<div/>`)
|
||||||
expect(root.hoists.length).toBe(0)
|
expect(root.hoists.length).toBe(0)
|
||||||
expect(args).toEqual([`"div"`])
|
expect(root.codegenNode).toMatchObject({
|
||||||
|
tag: `"div"`
|
||||||
|
})
|
||||||
expect(generate(root).code).toMatchSnapshot()
|
expect(generate(root).code).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('hoist simple element', () => {
|
test('hoist simple element', () => {
|
||||||
const { root, args } = transformWithHoist(
|
const root = transformWithHoist(
|
||||||
`<div><span class="inline">hello</span></div>`
|
`<div><span class="inline">hello</span></div>`
|
||||||
)
|
)
|
||||||
expect(root.hoists).toMatchObject([
|
expect(root.hoists).toMatchObject([
|
||||||
{
|
{
|
||||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
type: NodeTypes.VNODE_CALL,
|
||||||
callee: CREATE_VNODE,
|
tag: `"span"`,
|
||||||
arguments: [
|
props: createObjectMatcher({ class: 'inline' }),
|
||||||
`"span"`,
|
children: {
|
||||||
createObjectMatcher({ class: 'inline' }),
|
type: NodeTypes.TEXT,
|
||||||
{
|
content: `hello`
|
||||||
type: NodeTypes.TEXT,
|
}
|
||||||
content: `hello`
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
expect(args).toMatchObject([
|
expect(root.codegenNode).toMatchObject({
|
||||||
`"div"`,
|
tag: `"div"`,
|
||||||
`null`,
|
props: undefined,
|
||||||
[
|
children: [
|
||||||
{
|
{
|
||||||
type: NodeTypes.ELEMENT,
|
type: NodeTypes.ELEMENT,
|
||||||
codegenNode: {
|
codegenNode: {
|
||||||
@ -97,29 +83,24 @@ describe('compiler: hoistStatic transform', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
])
|
})
|
||||||
expect(generate(root).code).toMatchSnapshot()
|
expect(generate(root).code).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('hoist nested static tree', () => {
|
test('hoist nested static tree', () => {
|
||||||
const { root, args } = transformWithHoist(
|
const root = transformWithHoist(`<div><p><span/><span/></p></div>`)
|
||||||
`<div><p><span/><span/></p></div>`
|
|
||||||
)
|
|
||||||
expect(root.hoists).toMatchObject([
|
expect(root.hoists).toMatchObject([
|
||||||
{
|
{
|
||||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
type: NodeTypes.VNODE_CALL,
|
||||||
callee: CREATE_VNODE,
|
tag: `"p"`,
|
||||||
arguments: [
|
props: undefined,
|
||||||
`"p"`,
|
children: [
|
||||||
`null`,
|
{ type: NodeTypes.ELEMENT, tag: `span` },
|
||||||
[
|
{ type: NodeTypes.ELEMENT, tag: `span` }
|
||||||
{ type: NodeTypes.ELEMENT, tag: `span` },
|
|
||||||
{ type: NodeTypes.ELEMENT, tag: `span` }
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
expect(args[2]).toMatchObject([
|
expect((root.codegenNode as VNodeCall).children).toMatchObject([
|
||||||
{
|
{
|
||||||
type: NodeTypes.ELEMENT,
|
type: NodeTypes.ELEMENT,
|
||||||
codegenNode: {
|
codegenNode: {
|
||||||
@ -132,21 +113,16 @@ describe('compiler: hoistStatic transform', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('hoist nested static tree with comments', () => {
|
test('hoist nested static tree with comments', () => {
|
||||||
const { root, args } = transformWithHoist(
|
const root = transformWithHoist(`<div><div><!--comment--></div></div>`)
|
||||||
`<div><div><!--comment--></div></div>`
|
|
||||||
)
|
|
||||||
expect(root.hoists).toMatchObject([
|
expect(root.hoists).toMatchObject([
|
||||||
{
|
{
|
||||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
type: NodeTypes.VNODE_CALL,
|
||||||
callee: CREATE_VNODE,
|
tag: `"div"`,
|
||||||
arguments: [
|
props: undefined,
|
||||||
`"div"`,
|
children: [{ type: NodeTypes.COMMENT, content: `comment` }]
|
||||||
`null`,
|
|
||||||
[{ type: NodeTypes.COMMENT, content: `comment` }]
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
expect(args[2]).toMatchObject([
|
expect((root.codegenNode as VNodeCall).children).toMatchObject([
|
||||||
{
|
{
|
||||||
type: NodeTypes.ELEMENT,
|
type: NodeTypes.ELEMENT,
|
||||||
codegenNode: {
|
codegenNode: {
|
||||||
@ -159,20 +135,18 @@ describe('compiler: hoistStatic transform', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('hoist siblings with common non-hoistable parent', () => {
|
test('hoist siblings with common non-hoistable parent', () => {
|
||||||
const { root, args } = transformWithHoist(`<div><span/><div/></div>`)
|
const root = transformWithHoist(`<div><span/><div/></div>`)
|
||||||
expect(root.hoists).toMatchObject([
|
expect(root.hoists).toMatchObject([
|
||||||
{
|
{
|
||||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
type: NodeTypes.VNODE_CALL,
|
||||||
callee: CREATE_VNODE,
|
tag: `"span"`
|
||||||
arguments: [`"span"`]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
type: NodeTypes.VNODE_CALL,
|
||||||
callee: CREATE_VNODE,
|
tag: `"div"`
|
||||||
arguments: [`"div"`]
|
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
expect(args[2]).toMatchObject([
|
expect((root.codegenNode as VNodeCall).children).toMatchObject([
|
||||||
{
|
{
|
||||||
type: NodeTypes.ELEMENT,
|
type: NodeTypes.ELEMENT,
|
||||||
codegenNode: {
|
codegenNode: {
|
||||||
@ -192,14 +166,14 @@ describe('compiler: hoistStatic transform', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('should NOT hoist components', () => {
|
test('should NOT hoist components', () => {
|
||||||
const { root, args } = transformWithHoist(`<div><Comp/></div>`)
|
const root = transformWithHoist(`<div><Comp/></div>`)
|
||||||
expect(root.hoists.length).toBe(0)
|
expect(root.hoists.length).toBe(0)
|
||||||
expect(args[2]).toMatchObject([
|
expect((root.codegenNode as VNodeCall).children).toMatchObject([
|
||||||
{
|
{
|
||||||
type: NodeTypes.ELEMENT,
|
type: NodeTypes.ELEMENT,
|
||||||
codegenNode: {
|
codegenNode: {
|
||||||
callee: CREATE_VNODE,
|
type: NodeTypes.VNODE_CALL,
|
||||||
arguments: [`_component_Comp`]
|
tag: `_component_Comp`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
@ -207,22 +181,20 @@ describe('compiler: hoistStatic transform', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('should NOT hoist element with dynamic props', () => {
|
test('should NOT hoist element with dynamic props', () => {
|
||||||
const { root, args } = transformWithHoist(`<div><div :id="foo"/></div>`)
|
const root = transformWithHoist(`<div><div :id="foo"/></div>`)
|
||||||
expect(root.hoists.length).toBe(0)
|
expect(root.hoists.length).toBe(0)
|
||||||
expect(args[2]).toMatchObject([
|
expect((root.codegenNode as VNodeCall).children).toMatchObject([
|
||||||
{
|
{
|
||||||
type: NodeTypes.ELEMENT,
|
type: NodeTypes.ELEMENT,
|
||||||
codegenNode: {
|
codegenNode: {
|
||||||
callee: CREATE_VNODE,
|
type: NodeTypes.VNODE_CALL,
|
||||||
arguments: [
|
tag: `"div"`,
|
||||||
`"div"`,
|
props: createObjectMatcher({
|
||||||
createObjectMatcher({
|
id: `[foo]`
|
||||||
id: `[foo]`
|
}),
|
||||||
}),
|
children: undefined,
|
||||||
`null`,
|
patchFlag: genFlagText(PatchFlags.PROPS),
|
||||||
genFlagText(PatchFlags.PROPS),
|
dynamicProps: `["id"]`
|
||||||
`["id"]`
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
@ -230,19 +202,19 @@ describe('compiler: hoistStatic transform', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('hoist element with static key', () => {
|
test('hoist element with static key', () => {
|
||||||
const { root, args } = transformWithHoist(`<div><div key="foo"/></div>`)
|
const root = transformWithHoist(`<div><div key="foo"/></div>`)
|
||||||
expect(root.hoists.length).toBe(1)
|
expect(root.hoists.length).toBe(1)
|
||||||
expect(root.hoists).toMatchObject([
|
expect(root.hoists).toMatchObject([
|
||||||
{
|
{
|
||||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
type: NodeTypes.VNODE_CALL,
|
||||||
callee: CREATE_VNODE,
|
tag: `"div"`,
|
||||||
arguments: [`"div"`, createObjectMatcher({ key: 'foo' })]
|
props: createObjectMatcher({ key: 'foo' })
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
expect(args).toMatchObject([
|
expect(root.codegenNode).toMatchObject({
|
||||||
`"div"`,
|
tag: `"div"`,
|
||||||
`null`,
|
props: undefined,
|
||||||
[
|
children: [
|
||||||
{
|
{
|
||||||
type: NodeTypes.ELEMENT,
|
type: NodeTypes.ELEMENT,
|
||||||
codegenNode: {
|
codegenNode: {
|
||||||
@ -251,24 +223,22 @@ describe('compiler: hoistStatic transform', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
])
|
})
|
||||||
expect(generate(root).code).toMatchSnapshot()
|
expect(generate(root).code).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should NOT hoist element with dynamic key', () => {
|
test('should NOT hoist element with dynamic key', () => {
|
||||||
const { root, args } = transformWithHoist(`<div><div :key="foo"/></div>`)
|
const root = transformWithHoist(`<div><div :key="foo"/></div>`)
|
||||||
expect(root.hoists.length).toBe(0)
|
expect(root.hoists.length).toBe(0)
|
||||||
expect(args[2]).toMatchObject([
|
expect((root.codegenNode as VNodeCall).children).toMatchObject([
|
||||||
{
|
{
|
||||||
type: NodeTypes.ELEMENT,
|
type: NodeTypes.ELEMENT,
|
||||||
codegenNode: {
|
codegenNode: {
|
||||||
callee: CREATE_VNODE,
|
type: NodeTypes.VNODE_CALL,
|
||||||
arguments: [
|
tag: `"div"`,
|
||||||
`"div"`,
|
props: createObjectMatcher({
|
||||||
createObjectMatcher({
|
key: `[foo]`
|
||||||
key: `[foo]`
|
})
|
||||||
})
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
@ -276,21 +246,19 @@ describe('compiler: hoistStatic transform', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('should NOT hoist element with dynamic ref', () => {
|
test('should NOT hoist element with dynamic ref', () => {
|
||||||
const { root, args } = transformWithHoist(`<div><div :ref="foo"/></div>`)
|
const root = transformWithHoist(`<div><div :ref="foo"/></div>`)
|
||||||
expect(root.hoists.length).toBe(0)
|
expect(root.hoists.length).toBe(0)
|
||||||
expect(args[2]).toMatchObject([
|
expect((root.codegenNode as VNodeCall).children).toMatchObject([
|
||||||
{
|
{
|
||||||
type: NodeTypes.ELEMENT,
|
type: NodeTypes.ELEMENT,
|
||||||
codegenNode: {
|
codegenNode: {
|
||||||
callee: CREATE_VNODE,
|
type: NodeTypes.VNODE_CALL,
|
||||||
arguments: [
|
tag: `"div"`,
|
||||||
`"div"`,
|
props: createObjectMatcher({
|
||||||
createObjectMatcher({
|
ref: `[foo]`
|
||||||
ref: `[foo]`
|
}),
|
||||||
}),
|
children: undefined,
|
||||||
`null`,
|
patchFlag: genFlagText(PatchFlags.NEED_PATCH)
|
||||||
genFlagText(PatchFlags.NEED_PATCH)
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
@ -298,32 +266,23 @@ describe('compiler: hoistStatic transform', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('hoist static props for elements with directives', () => {
|
test('hoist static props for elements with directives', () => {
|
||||||
const { root, args } = transformWithHoist(
|
const root = transformWithHoist(`<div><div id="foo" v-foo/></div>`)
|
||||||
`<div><div id="foo" v-foo/></div>`
|
|
||||||
)
|
|
||||||
expect(root.hoists).toMatchObject([createObjectMatcher({ id: 'foo' })])
|
expect(root.hoists).toMatchObject([createObjectMatcher({ id: 'foo' })])
|
||||||
expect(args[2]).toMatchObject([
|
expect((root.codegenNode as VNodeCall).children).toMatchObject([
|
||||||
{
|
{
|
||||||
type: NodeTypes.ELEMENT,
|
type: NodeTypes.ELEMENT,
|
||||||
codegenNode: {
|
codegenNode: {
|
||||||
callee: WITH_DIRECTIVES,
|
type: NodeTypes.VNODE_CALL,
|
||||||
arguments: [
|
tag: `"div"`,
|
||||||
{
|
props: {
|
||||||
callee: CREATE_VNODE,
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
arguments: [
|
content: `_hoisted_1`
|
||||||
`"div"`,
|
},
|
||||||
{
|
children: undefined,
|
||||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
patchFlag: genFlagText(PatchFlags.NEED_PATCH),
|
||||||
content: `_hoisted_1`
|
directives: {
|
||||||
},
|
type: NodeTypes.JS_ARRAY_EXPRESSION
|
||||||
`null`,
|
}
|
||||||
genFlagText(PatchFlags.NEED_PATCH)
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: NodeTypes.JS_ARRAY_EXPRESSION
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
@ -331,21 +290,19 @@ describe('compiler: hoistStatic transform', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('hoist static props for elements with dynamic text children', () => {
|
test('hoist static props for elements with dynamic text children', () => {
|
||||||
const { root, args } = transformWithHoist(
|
const root = transformWithHoist(
|
||||||
`<div><div id="foo">{{ hello }}</div></div>`
|
`<div><div id="foo">{{ hello }}</div></div>`
|
||||||
)
|
)
|
||||||
expect(root.hoists).toMatchObject([createObjectMatcher({ id: 'foo' })])
|
expect(root.hoists).toMatchObject([createObjectMatcher({ id: 'foo' })])
|
||||||
expect(args[2]).toMatchObject([
|
expect((root.codegenNode as VNodeCall).children).toMatchObject([
|
||||||
{
|
{
|
||||||
type: NodeTypes.ELEMENT,
|
type: NodeTypes.ELEMENT,
|
||||||
codegenNode: {
|
codegenNode: {
|
||||||
callee: CREATE_VNODE,
|
type: NodeTypes.VNODE_CALL,
|
||||||
arguments: [
|
tag: `"div"`,
|
||||||
`"div"`,
|
props: { content: `_hoisted_1` },
|
||||||
{ content: `_hoisted_1` },
|
children: { type: NodeTypes.INTERPOLATION },
|
||||||
{ type: NodeTypes.INTERPOLATION },
|
patchFlag: genFlagText(PatchFlags.TEXT)
|
||||||
genFlagText(PatchFlags.TEXT)
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
@ -353,20 +310,16 @@ describe('compiler: hoistStatic transform', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('hoist static props for elements with unhoistable children', () => {
|
test('hoist static props for elements with unhoistable children', () => {
|
||||||
const { root, args } = transformWithHoist(
|
const root = transformWithHoist(`<div><div id="foo"><Comp/></div></div>`)
|
||||||
`<div><div id="foo"><Comp/></div></div>`
|
|
||||||
)
|
|
||||||
expect(root.hoists).toMatchObject([createObjectMatcher({ id: 'foo' })])
|
expect(root.hoists).toMatchObject([createObjectMatcher({ id: 'foo' })])
|
||||||
expect(args[2]).toMatchObject([
|
expect((root.codegenNode as VNodeCall).children).toMatchObject([
|
||||||
{
|
{
|
||||||
type: NodeTypes.ELEMENT,
|
type: NodeTypes.ELEMENT,
|
||||||
codegenNode: {
|
codegenNode: {
|
||||||
callee: CREATE_VNODE,
|
type: NodeTypes.VNODE_CALL,
|
||||||
arguments: [
|
tag: `"div"`,
|
||||||
`"div"`,
|
props: { content: `_hoisted_1` },
|
||||||
{ content: `_hoisted_1` },
|
children: [{ type: NodeTypes.ELEMENT, tag: `Comp` }]
|
||||||
[{ type: NodeTypes.ELEMENT, tag: `Comp` }]
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
@ -374,7 +327,7 @@ describe('compiler: hoistStatic transform', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('should hoist v-if props/children if static', () => {
|
test('should hoist v-if props/children if static', () => {
|
||||||
const { root, args } = transformWithHoist(
|
const root = transformWithHoist(
|
||||||
`<div><div v-if="ok" id="foo"><span/></div></div>`
|
`<div><div v-if="ok" id="foo"><span/></div></div>`
|
||||||
)
|
)
|
||||||
expect(root.hoists).toMatchObject([
|
expect(root.hoists).toMatchObject([
|
||||||
@ -383,37 +336,31 @@ describe('compiler: hoistStatic transform', () => {
|
|||||||
id: 'foo'
|
id: 'foo'
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
callee: CREATE_VNODE,
|
type: NodeTypes.VNODE_CALL,
|
||||||
arguments: [`"span"`]
|
tag: `"span"`
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
expect(args[2][0].codegenNode).toMatchObject({
|
expect(
|
||||||
type: NodeTypes.JS_SEQUENCE_EXPRESSION,
|
((root.children[0] as ElementNode).children[0] as IfNode).codegenNode
|
||||||
expressions: [
|
).toMatchObject({
|
||||||
{ callee: OPEN_BLOCK },
|
type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
|
||||||
{
|
consequent: {
|
||||||
type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
|
// blocks should NOT be hoisted
|
||||||
consequent: {
|
type: NodeTypes.VNODE_CALL,
|
||||||
// blocks should NOT be hoisted
|
tag: `"div"`,
|
||||||
callee: CREATE_BLOCK,
|
props: { content: `_hoisted_1` },
|
||||||
arguments: [
|
children: [
|
||||||
`"div"`,
|
{
|
||||||
{ content: `_hoisted_1` },
|
codegenNode: { content: `_hoisted_2` }
|
||||||
[
|
|
||||||
{
|
|
||||||
codegenNode: { content: `_hoisted_2` }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
]
|
||||||
]
|
}
|
||||||
})
|
})
|
||||||
expect(generate(root).code).toMatchSnapshot()
|
expect(generate(root).code).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should hoist v-for children if static', () => {
|
test('should hoist v-for children if static', () => {
|
||||||
const { root, args } = transformWithHoist(
|
const root = transformWithHoist(
|
||||||
`<div><div v-for="i in list" id="foo"><span/></div></div>`
|
`<div><div v-for="i in list" id="foo"><span/></div></div>`
|
||||||
)
|
)
|
||||||
expect(root.hoists).toMatchObject([
|
expect(root.hoists).toMatchObject([
|
||||||
@ -421,55 +368,58 @@ describe('compiler: hoistStatic transform', () => {
|
|||||||
id: 'foo'
|
id: 'foo'
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
callee: CREATE_VNODE,
|
type: NodeTypes.VNODE_CALL,
|
||||||
arguments: [`"span"`]
|
tag: `"span"`
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
const forBlockCodegen = args[2][0].codegenNode
|
const forBlockCodegen = ((root.children[0] as ElementNode)
|
||||||
|
.children[0] as ForNode).codegenNode
|
||||||
expect(forBlockCodegen).toMatchObject({
|
expect(forBlockCodegen).toMatchObject({
|
||||||
type: NodeTypes.JS_SEQUENCE_EXPRESSION,
|
type: NodeTypes.VNODE_CALL,
|
||||||
expressions: [
|
tag: FRAGMENT,
|
||||||
{ callee: OPEN_BLOCK },
|
props: undefined,
|
||||||
{
|
children: {
|
||||||
callee: CREATE_BLOCK,
|
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||||
arguments: [
|
callee: RENDER_LIST
|
||||||
FRAGMENT,
|
},
|
||||||
`null`,
|
patchFlag: genFlagText(PatchFlags.UNKEYED_FRAGMENT)
|
||||||
{
|
|
||||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
|
||||||
callee: RENDER_LIST
|
|
||||||
},
|
|
||||||
genFlagText(PatchFlags.UNKEYED_FRAGMENT)
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
})
|
||||||
const innerBlockCodegen =
|
const innerBlockCodegen = forBlockCodegen!.children.arguments[1]
|
||||||
forBlockCodegen.expressions[1].arguments[2].arguments[1].returns
|
expect(innerBlockCodegen.returns).toMatchObject({
|
||||||
expect(innerBlockCodegen).toMatchObject({
|
type: NodeTypes.VNODE_CALL,
|
||||||
type: NodeTypes.JS_SEQUENCE_EXPRESSION,
|
tag: `"div"`,
|
||||||
expressions: [
|
props: { content: `_hoisted_1` },
|
||||||
{ callee: OPEN_BLOCK },
|
children: [
|
||||||
{
|
{
|
||||||
callee: CREATE_BLOCK,
|
codegenNode: { content: `_hoisted_2` }
|
||||||
arguments: [
|
|
||||||
`"div"`,
|
|
||||||
{ content: `_hoisted_1` },
|
|
||||||
[
|
|
||||||
{
|
|
||||||
codegenNode: { content: `_hoisted_2` }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
expect(generate(root).code).toMatchSnapshot()
|
expect(generate(root).code).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('hoist static text node between elements', () => {
|
||||||
|
const root = transformWithHoist(`<div>static<div>static</div></div>`)
|
||||||
|
expect(root.hoists).toMatchObject([
|
||||||
|
{
|
||||||
|
callee: CREATE_TEXT,
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
type: NodeTypes.TEXT,
|
||||||
|
content: `static`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: NodeTypes.VNODE_CALL,
|
||||||
|
tag: `"div"`
|
||||||
|
}
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
describe('prefixIdentifiers', () => {
|
describe('prefixIdentifiers', () => {
|
||||||
test('hoist nested static tree with static interpolation', () => {
|
test('hoist nested static tree with static interpolation', () => {
|
||||||
const { root, args } = transformWithHoist(
|
const root = transformWithHoist(
|
||||||
`<div><span>foo {{ 1 }} {{ true }}</span></div>`,
|
`<div><span>foo {{ 1 }} {{ true }}</span></div>`,
|
||||||
{
|
{
|
||||||
prefixIdentifiers: true
|
prefixIdentifiers: true
|
||||||
@ -477,44 +427,18 @@ describe('compiler: hoistStatic transform', () => {
|
|||||||
)
|
)
|
||||||
expect(root.hoists).toMatchObject([
|
expect(root.hoists).toMatchObject([
|
||||||
{
|
{
|
||||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
type: NodeTypes.VNODE_CALL,
|
||||||
callee: CREATE_VNODE,
|
tag: `"span"`,
|
||||||
arguments: [
|
props: undefined,
|
||||||
`"span"`,
|
children: {
|
||||||
`null`,
|
type: NodeTypes.COMPOUND_EXPRESSION
|
||||||
[
|
}
|
||||||
{
|
|
||||||
type: NodeTypes.TEXT,
|
|
||||||
content: `foo `
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: NodeTypes.INTERPOLATION,
|
|
||||||
content: {
|
|
||||||
content: `1`,
|
|
||||||
isStatic: false,
|
|
||||||
isConstant: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: NodeTypes.TEXT,
|
|
||||||
content: ` `
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: NodeTypes.INTERPOLATION,
|
|
||||||
content: {
|
|
||||||
content: `true`,
|
|
||||||
isStatic: false,
|
|
||||||
isConstant: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
expect(args).toMatchObject([
|
expect(root.codegenNode).toMatchObject({
|
||||||
`"div"`,
|
tag: `"div"`,
|
||||||
`null`,
|
props: undefined,
|
||||||
[
|
children: [
|
||||||
{
|
{
|
||||||
type: NodeTypes.ELEMENT,
|
type: NodeTypes.ELEMENT,
|
||||||
codegenNode: {
|
codegenNode: {
|
||||||
@ -523,12 +447,12 @@ describe('compiler: hoistStatic transform', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
])
|
})
|
||||||
expect(generate(root).code).toMatchSnapshot()
|
expect(generate(root).code).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('hoist nested static tree with static prop value', () => {
|
test('hoist nested static tree with static prop value', () => {
|
||||||
const { root, args } = transformWithHoist(
|
const root = transformWithHoist(
|
||||||
`<div><span :foo="0">{{ 1 }}</span></div>`,
|
`<div><span :foo="0">{{ 1 }}</span></div>`,
|
||||||
{
|
{
|
||||||
prefixIdentifiers: true
|
prefixIdentifiers: true
|
||||||
@ -537,26 +461,23 @@ describe('compiler: hoistStatic transform', () => {
|
|||||||
|
|
||||||
expect(root.hoists).toMatchObject([
|
expect(root.hoists).toMatchObject([
|
||||||
{
|
{
|
||||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
type: NodeTypes.VNODE_CALL,
|
||||||
callee: CREATE_VNODE,
|
tag: `"span"`,
|
||||||
arguments: [
|
props: createObjectMatcher({ foo: `[0]` }),
|
||||||
`"span"`,
|
children: {
|
||||||
createObjectMatcher({ foo: `[0]` }),
|
type: NodeTypes.INTERPOLATION,
|
||||||
{
|
content: {
|
||||||
type: NodeTypes.INTERPOLATION,
|
content: `1`,
|
||||||
content: {
|
isStatic: false,
|
||||||
content: `1`,
|
isConstant: true
|
||||||
isStatic: false,
|
|
||||||
isConstant: true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
expect(args).toMatchObject([
|
expect(root.codegenNode).toMatchObject({
|
||||||
`"div"`,
|
tag: `"div"`,
|
||||||
`null`,
|
props: undefined,
|
||||||
[
|
children: [
|
||||||
{
|
{
|
||||||
type: NodeTypes.ELEMENT,
|
type: NodeTypes.ELEMENT,
|
||||||
codegenNode: {
|
codegenNode: {
|
||||||
@ -565,12 +486,12 @@ describe('compiler: hoistStatic transform', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
])
|
})
|
||||||
expect(generate(root).code).toMatchSnapshot()
|
expect(generate(root).code).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('hoist class with static object value', () => {
|
test('hoist class with static object value', () => {
|
||||||
const { root, args } = transformWithHoist(
|
const root = transformWithHoist(
|
||||||
`<div><span :class="{ foo: true }">{{ bar }}</span></div>`,
|
`<div><span :class="{ foo: true }">{{ bar }}</span></div>`,
|
||||||
{
|
{
|
||||||
prefixIdentifiers: true
|
prefixIdentifiers: true
|
||||||
@ -596,39 +517,37 @@ describe('compiler: hoistStatic transform', () => {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
expect(args).toMatchObject([
|
expect(root.codegenNode).toMatchObject({
|
||||||
`"div"`,
|
tag: `"div"`,
|
||||||
`null`,
|
props: undefined,
|
||||||
[
|
children: [
|
||||||
{
|
{
|
||||||
type: NodeTypes.ELEMENT,
|
type: NodeTypes.ELEMENT,
|
||||||
codegenNode: {
|
codegenNode: {
|
||||||
callee: CREATE_VNODE,
|
type: NodeTypes.VNODE_CALL,
|
||||||
arguments: [
|
tag: `"span"`,
|
||||||
`"span"`,
|
props: {
|
||||||
{
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
content: `_hoisted_1`
|
||||||
content: `_hoisted_1`
|
},
|
||||||
},
|
children: {
|
||||||
{
|
type: NodeTypes.INTERPOLATION,
|
||||||
type: NodeTypes.INTERPOLATION,
|
content: {
|
||||||
content: {
|
content: `_ctx.bar`,
|
||||||
content: `_ctx.bar`,
|
isConstant: false,
|
||||||
isConstant: false,
|
isStatic: false
|
||||||
isStatic: false
|
}
|
||||||
}
|
},
|
||||||
},
|
patchFlag: `1 /* TEXT */`
|
||||||
`1 /* TEXT */`
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
])
|
})
|
||||||
expect(generate(root).code).toMatchSnapshot()
|
expect(generate(root).code).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should NOT hoist expressions that refer scope variables', () => {
|
test('should NOT hoist expressions that refer scope variables', () => {
|
||||||
const { root } = transformWithHoist(
|
const root = transformWithHoist(
|
||||||
`<div><p v-for="o in list"><span>{{ o }}</span></p></div>`,
|
`<div><p v-for="o in list"><span>{{ o }}</span></p></div>`,
|
||||||
{
|
{
|
||||||
prefixIdentifiers: true
|
prefixIdentifiers: true
|
||||||
@ -640,7 +559,7 @@ describe('compiler: hoistStatic transform', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('should NOT hoist expressions that refer scope variables (2)', () => {
|
test('should NOT hoist expressions that refer scope variables (2)', () => {
|
||||||
const { root } = transformWithHoist(
|
const root = transformWithHoist(
|
||||||
`<div><p v-for="o in list"><span>{{ o + 'foo' }}</span></p></div>`,
|
`<div><p v-for="o in list"><span>{{ o + 'foo' }}</span></p></div>`,
|
||||||
{
|
{
|
||||||
prefixIdentifiers: true
|
prefixIdentifiers: true
|
||||||
@ -652,7 +571,7 @@ describe('compiler: hoistStatic transform', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('should NOT hoist expressions that refer scope variables (v-slot)', () => {
|
test('should NOT hoist expressions that refer scope variables (v-slot)', () => {
|
||||||
const { root } = transformWithHoist(
|
const root = transformWithHoist(
|
||||||
`<Comp v-slot="{ foo }">{{ foo }}</Comp>`,
|
`<Comp v-slot="{ foo }">{{ foo }}</Comp>`,
|
||||||
{
|
{
|
||||||
prefixIdentifiers: true
|
prefixIdentifiers: true
|
||||||
@ -664,7 +583,7 @@ describe('compiler: hoistStatic transform', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('should NOT hoist elements with cached handlers', () => {
|
test('should NOT hoist elements with cached handlers', () => {
|
||||||
const { root } = transformWithHoist(
|
const root = transformWithHoist(
|
||||||
`<div><div><div @click="foo"/></div></div>`,
|
`<div><div><div @click="foo"/></div></div>`,
|
||||||
{
|
{
|
||||||
prefixIdentifiers: true,
|
prefixIdentifiers: true,
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
import {
|
||||||
|
baseParse as parse,
|
||||||
|
transform,
|
||||||
|
ElementNode,
|
||||||
|
noopDirectiveTransform,
|
||||||
|
VNodeCall
|
||||||
|
} from '../../src'
|
||||||
|
import { transformElement } from '../../src/transforms/transformElement'
|
||||||
|
|
||||||
|
describe('compiler: noop directive transform', () => {
|
||||||
|
test('should add no props to DOM', () => {
|
||||||
|
const ast = parse(`<div v-noop/>`)
|
||||||
|
transform(ast, {
|
||||||
|
nodeTransforms: [transformElement],
|
||||||
|
directiveTransforms: {
|
||||||
|
noop: noopDirectiveTransform
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const node = ast.children[0] as ElementNode
|
||||||
|
// As v-noop adds no properties the codegen should be identical to
|
||||||
|
// rendering a div with no props or reactive data (so just the tag as the arg)
|
||||||
|
expect((node.codegenNode as VNodeCall).props).toBeUndefined()
|
||||||
|
})
|
||||||
|
})
|
@ -1,24 +1,28 @@
|
|||||||
import { CompilerOptions, parse, transform, ErrorCodes } from '../../src'
|
import {
|
||||||
|
CompilerOptions,
|
||||||
|
baseParse as parse,
|
||||||
|
transform,
|
||||||
|
ErrorCodes
|
||||||
|
} from '../../src'
|
||||||
import {
|
import {
|
||||||
RESOLVE_COMPONENT,
|
RESOLVE_COMPONENT,
|
||||||
CREATE_VNODE,
|
CREATE_VNODE,
|
||||||
MERGE_PROPS,
|
MERGE_PROPS,
|
||||||
RESOLVE_DIRECTIVE,
|
RESOLVE_DIRECTIVE,
|
||||||
WITH_DIRECTIVES,
|
|
||||||
TO_HANDLERS,
|
TO_HANDLERS,
|
||||||
helperNameMap,
|
helperNameMap,
|
||||||
PORTAL,
|
TELEPORT,
|
||||||
RESOLVE_DYNAMIC_COMPONENT,
|
RESOLVE_DYNAMIC_COMPONENT,
|
||||||
SUSPENSE,
|
SUSPENSE,
|
||||||
KEEP_ALIVE,
|
KEEP_ALIVE,
|
||||||
BASE_TRANSITION
|
BASE_TRANSITION
|
||||||
} from '../../src/runtimeHelpers'
|
} from '../../src/runtimeHelpers'
|
||||||
import {
|
import {
|
||||||
CallExpression,
|
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
createObjectProperty,
|
createObjectProperty,
|
||||||
DirectiveNode,
|
DirectiveNode,
|
||||||
RootNode
|
RootNode,
|
||||||
|
VNodeCall
|
||||||
} from '../../src/ast'
|
} from '../../src/ast'
|
||||||
import { transformElement } from '../../src/transforms/transformElement'
|
import { transformElement } from '../../src/transforms/transformElement'
|
||||||
import { transformStyle } from '../../../compiler-dom/src/transforms/transformStyle'
|
import { transformStyle } from '../../../compiler-dom/src/transforms/transformStyle'
|
||||||
@ -33,7 +37,7 @@ function parseWithElementTransform(
|
|||||||
options: CompilerOptions = {}
|
options: CompilerOptions = {}
|
||||||
): {
|
): {
|
||||||
root: RootNode
|
root: RootNode
|
||||||
node: CallExpression
|
node: VNodeCall
|
||||||
} {
|
} {
|
||||||
// wrap raw template in an extra div so that it doesn't get turned into a
|
// wrap raw template in an extra div so that it doesn't get turned into a
|
||||||
// block as root node
|
// block as root node
|
||||||
@ -43,8 +47,8 @@ function parseWithElementTransform(
|
|||||||
...options
|
...options
|
||||||
})
|
})
|
||||||
const codegenNode = (ast as any).children[0].children[0]
|
const codegenNode = (ast as any).children[0].children[0]
|
||||||
.codegenNode as CallExpression
|
.codegenNode as VNodeCall
|
||||||
expect(codegenNode.type).toBe(NodeTypes.JS_CALL_EXPRESSION)
|
expect(codegenNode.type).toBe(NodeTypes.VNODE_CALL)
|
||||||
return {
|
return {
|
||||||
root: ast,
|
root: ast,
|
||||||
node: codegenNode
|
node: codegenNode
|
||||||
@ -68,63 +72,63 @@ describe('compiler: element transform', () => {
|
|||||||
|
|
||||||
test('static props', () => {
|
test('static props', () => {
|
||||||
const { node } = parseWithElementTransform(`<div id="foo" class="bar" />`)
|
const { node } = parseWithElementTransform(`<div id="foo" class="bar" />`)
|
||||||
expect(node.callee).toBe(CREATE_VNODE)
|
expect(node).toMatchObject({
|
||||||
expect(node.arguments).toMatchObject([
|
tag: `"div"`,
|
||||||
`"div"`,
|
props: createObjectMatcher({
|
||||||
createObjectMatcher({
|
|
||||||
id: 'foo',
|
id: 'foo',
|
||||||
class: 'bar'
|
class: 'bar'
|
||||||
})
|
}),
|
||||||
])
|
children: undefined
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test('props + children', () => {
|
test('props + children', () => {
|
||||||
const { node } = parseWithElementTransform(`<div id="foo"><span/></div>`)
|
const { node } = parseWithElementTransform(`<div id="foo"><span/></div>`)
|
||||||
expect(node.callee).toBe(CREATE_VNODE)
|
|
||||||
expect(node.arguments).toMatchObject([
|
expect(node).toMatchObject({
|
||||||
`"div"`,
|
tag: `"div"`,
|
||||||
createObjectMatcher({
|
props: createObjectMatcher({
|
||||||
id: 'foo'
|
id: 'foo'
|
||||||
}),
|
}),
|
||||||
[
|
children: [
|
||||||
{
|
{
|
||||||
type: NodeTypes.ELEMENT,
|
type: NodeTypes.ELEMENT,
|
||||||
tag: 'span',
|
tag: 'span',
|
||||||
codegenNode: {
|
codegenNode: {
|
||||||
callee: CREATE_VNODE,
|
type: NodeTypes.VNODE_CALL,
|
||||||
arguments: [`"span"`]
|
tag: `"span"`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
])
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test('0 placeholder for children with no props', () => {
|
test('0 placeholder for children with no props', () => {
|
||||||
const { node } = parseWithElementTransform(`<div><span/></div>`)
|
const { node } = parseWithElementTransform(`<div><span/></div>`)
|
||||||
expect(node.callee).toBe(CREATE_VNODE)
|
|
||||||
expect(node.arguments).toMatchObject([
|
expect(node).toMatchObject({
|
||||||
`"div"`,
|
tag: `"div"`,
|
||||||
`null`,
|
props: undefined,
|
||||||
[
|
children: [
|
||||||
{
|
{
|
||||||
type: NodeTypes.ELEMENT,
|
type: NodeTypes.ELEMENT,
|
||||||
tag: 'span',
|
tag: 'span',
|
||||||
codegenNode: {
|
codegenNode: {
|
||||||
callee: CREATE_VNODE,
|
type: NodeTypes.VNODE_CALL,
|
||||||
arguments: [`"span"`]
|
tag: `"span"`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
])
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test('v-bind="obj"', () => {
|
test('v-bind="obj"', () => {
|
||||||
const { root, node } = parseWithElementTransform(`<div v-bind="obj" />`)
|
const { root, node } = parseWithElementTransform(`<div v-bind="obj" />`)
|
||||||
// single v-bind doesn't need mergeProps
|
// single v-bind doesn't need mergeProps
|
||||||
expect(root.helpers).not.toContain(MERGE_PROPS)
|
expect(root.helpers).not.toContain(MERGE_PROPS)
|
||||||
expect(node.callee).toBe(CREATE_VNODE)
|
|
||||||
// should directly use `obj` in props position
|
// should directly use `obj` in props position
|
||||||
expect(node.arguments[1]).toMatchObject({
|
expect(node.props).toMatchObject({
|
||||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
content: `obj`
|
content: `obj`
|
||||||
})
|
})
|
||||||
@ -135,8 +139,8 @@ describe('compiler: element transform', () => {
|
|||||||
`<div id="foo" v-bind="obj" />`
|
`<div id="foo" v-bind="obj" />`
|
||||||
)
|
)
|
||||||
expect(root.helpers).toContain(MERGE_PROPS)
|
expect(root.helpers).toContain(MERGE_PROPS)
|
||||||
expect(node.callee).toBe(CREATE_VNODE)
|
|
||||||
expect(node.arguments[1]).toMatchObject({
|
expect(node.props).toMatchObject({
|
||||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||||
callee: MERGE_PROPS,
|
callee: MERGE_PROPS,
|
||||||
arguments: [
|
arguments: [
|
||||||
@ -156,8 +160,8 @@ describe('compiler: element transform', () => {
|
|||||||
`<div v-bind="obj" id="foo" />`
|
`<div v-bind="obj" id="foo" />`
|
||||||
)
|
)
|
||||||
expect(root.helpers).toContain(MERGE_PROPS)
|
expect(root.helpers).toContain(MERGE_PROPS)
|
||||||
expect(node.callee).toBe(CREATE_VNODE)
|
|
||||||
expect(node.arguments[1]).toMatchObject({
|
expect(node.props).toMatchObject({
|
||||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||||
callee: MERGE_PROPS,
|
callee: MERGE_PROPS,
|
||||||
arguments: [
|
arguments: [
|
||||||
@ -177,8 +181,8 @@ describe('compiler: element transform', () => {
|
|||||||
`<div id="foo" v-bind="obj" class="bar" />`
|
`<div id="foo" v-bind="obj" class="bar" />`
|
||||||
)
|
)
|
||||||
expect(root.helpers).toContain(MERGE_PROPS)
|
expect(root.helpers).toContain(MERGE_PROPS)
|
||||||
expect(node.callee).toBe(CREATE_VNODE)
|
|
||||||
expect(node.arguments[1]).toMatchObject({
|
expect(node.props).toMatchObject({
|
||||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||||
callee: MERGE_PROPS,
|
callee: MERGE_PROPS,
|
||||||
arguments: [
|
arguments: [
|
||||||
@ -201,8 +205,8 @@ describe('compiler: element transform', () => {
|
|||||||
`<div id="foo" v-on="obj" class="bar" />`
|
`<div id="foo" v-on="obj" class="bar" />`
|
||||||
)
|
)
|
||||||
expect(root.helpers).toContain(MERGE_PROPS)
|
expect(root.helpers).toContain(MERGE_PROPS)
|
||||||
expect(node.callee).toBe(CREATE_VNODE)
|
|
||||||
expect(node.arguments[1]).toMatchObject({
|
expect(node.props).toMatchObject({
|
||||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||||
callee: MERGE_PROPS,
|
callee: MERGE_PROPS,
|
||||||
arguments: [
|
arguments: [
|
||||||
@ -231,8 +235,8 @@ describe('compiler: element transform', () => {
|
|||||||
`<div id="foo" v-on="handlers" v-bind="obj" />`
|
`<div id="foo" v-on="handlers" v-bind="obj" />`
|
||||||
)
|
)
|
||||||
expect(root.helpers).toContain(MERGE_PROPS)
|
expect(root.helpers).toContain(MERGE_PROPS)
|
||||||
expect(node.callee).toBe(CREATE_VNODE)
|
|
||||||
expect(node.arguments[1]).toMatchObject({
|
expect(node.props).toMatchObject({
|
||||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||||
callee: MERGE_PROPS,
|
callee: MERGE_PROPS,
|
||||||
arguments: [
|
arguments: [
|
||||||
@ -259,43 +263,43 @@ describe('compiler: element transform', () => {
|
|||||||
|
|
||||||
test('should handle plain <template> as normal element', () => {
|
test('should handle plain <template> as normal element', () => {
|
||||||
const { node } = parseWithElementTransform(`<template id="foo" />`)
|
const { node } = parseWithElementTransform(`<template id="foo" />`)
|
||||||
expect(node.callee).toBe(CREATE_VNODE)
|
|
||||||
expect(node.arguments).toMatchObject([
|
expect(node).toMatchObject({
|
||||||
`"template"`,
|
tag: `"template"`,
|
||||||
createObjectMatcher({
|
props: createObjectMatcher({
|
||||||
id: 'foo'
|
id: 'foo'
|
||||||
})
|
})
|
||||||
])
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should handle <Portal> with normal children', () => {
|
test('should handle <Teleport> with normal children', () => {
|
||||||
function assert(tag: string) {
|
function assert(tag: string) {
|
||||||
const { root, node } = parseWithElementTransform(
|
const { root, node } = parseWithElementTransform(
|
||||||
`<${tag} target="#foo"><span /></${tag}>`
|
`<${tag} target="#foo"><span /></${tag}>`
|
||||||
)
|
)
|
||||||
expect(root.components.length).toBe(0)
|
expect(root.components.length).toBe(0)
|
||||||
expect(root.helpers).toContain(PORTAL)
|
expect(root.helpers).toContain(TELEPORT)
|
||||||
expect(node.callee).toBe(CREATE_VNODE)
|
|
||||||
expect(node.arguments).toMatchObject([
|
expect(node).toMatchObject({
|
||||||
PORTAL,
|
tag: TELEPORT,
|
||||||
createObjectMatcher({
|
props: createObjectMatcher({
|
||||||
target: '#foo'
|
target: '#foo'
|
||||||
}),
|
}),
|
||||||
[
|
children: [
|
||||||
{
|
{
|
||||||
type: NodeTypes.ELEMENT,
|
type: NodeTypes.ELEMENT,
|
||||||
tag: 'span',
|
tag: 'span',
|
||||||
codegenNode: {
|
codegenNode: {
|
||||||
callee: CREATE_VNODE,
|
type: NodeTypes.VNODE_CALL,
|
||||||
arguments: [`"span"`]
|
tag: `"span"`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
])
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(`portal`)
|
assert(`teleport`)
|
||||||
assert(`Portal`)
|
assert(`Teleport`)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should handle <Suspense>', () => {
|
test('should handle <Suspense>', () => {
|
||||||
@ -305,11 +309,11 @@ describe('compiler: element transform', () => {
|
|||||||
)
|
)
|
||||||
expect(root.components.length).toBe(0)
|
expect(root.components.length).toBe(0)
|
||||||
expect(root.helpers).toContain(SUSPENSE)
|
expect(root.helpers).toContain(SUSPENSE)
|
||||||
expect(node.callee).toBe(CREATE_VNODE)
|
|
||||||
expect(node.arguments).toMatchObject([
|
expect(node).toMatchObject({
|
||||||
SUSPENSE,
|
tag: SUSPENSE,
|
||||||
`null`,
|
props: undefined,
|
||||||
hasFallback
|
children: hasFallback
|
||||||
? createObjectMatcher({
|
? createObjectMatcher({
|
||||||
default: {
|
default: {
|
||||||
type: NodeTypes.JS_FUNCTION_EXPRESSION
|
type: NodeTypes.JS_FUNCTION_EXPRESSION
|
||||||
@ -317,15 +321,15 @@ describe('compiler: element transform', () => {
|
|||||||
fallback: {
|
fallback: {
|
||||||
type: NodeTypes.JS_FUNCTION_EXPRESSION
|
type: NodeTypes.JS_FUNCTION_EXPRESSION
|
||||||
},
|
},
|
||||||
_compiled: `[true]`
|
_: `[1]`
|
||||||
})
|
})
|
||||||
: createObjectMatcher({
|
: createObjectMatcher({
|
||||||
default: {
|
default: {
|
||||||
type: NodeTypes.JS_FUNCTION_EXPRESSION
|
type: NodeTypes.JS_FUNCTION_EXPRESSION
|
||||||
},
|
},
|
||||||
_compiled: `[true]`
|
_: `[1]`
|
||||||
})
|
})
|
||||||
])
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(`suspense`, `foo`)
|
assert(`suspense`, `foo`)
|
||||||
@ -339,18 +343,23 @@ describe('compiler: element transform', () => {
|
|||||||
|
|
||||||
test('should handle <KeepAlive>', () => {
|
test('should handle <KeepAlive>', () => {
|
||||||
function assert(tag: string) {
|
function assert(tag: string) {
|
||||||
const { root, node } = parseWithElementTransform(
|
const root = parse(`<div><${tag}><span /></${tag}></div>`)
|
||||||
`<${tag}><span /></${tag}>`
|
transform(root, {
|
||||||
)
|
nodeTransforms: [transformElement, transformText]
|
||||||
|
})
|
||||||
expect(root.components.length).toBe(0)
|
expect(root.components.length).toBe(0)
|
||||||
expect(root.helpers).toContain(KEEP_ALIVE)
|
expect(root.helpers).toContain(KEEP_ALIVE)
|
||||||
expect(node.callee).toBe(CREATE_VNODE)
|
const node = (root.children[0] as any).children[0].codegenNode
|
||||||
expect(node.arguments).toMatchObject([
|
expect(node).toMatchObject({
|
||||||
KEEP_ALIVE,
|
type: NodeTypes.VNODE_CALL,
|
||||||
`null`,
|
tag: KEEP_ALIVE,
|
||||||
|
isBlock: true, // should be forced into a block
|
||||||
|
props: undefined,
|
||||||
// keep-alive should not compile content to slots
|
// keep-alive should not compile content to slots
|
||||||
[{ type: NodeTypes.ELEMENT, tag: 'span' }]
|
children: [{ type: NodeTypes.ELEMENT, tag: 'span' }],
|
||||||
])
|
// should get a dynamic slots flag to force updates
|
||||||
|
patchFlag: genFlagText(PatchFlags.DYNAMIC_SLOTS)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(`keep-alive`)
|
assert(`keep-alive`)
|
||||||
@ -364,17 +373,17 @@ describe('compiler: element transform', () => {
|
|||||||
)
|
)
|
||||||
expect(root.components.length).toBe(0)
|
expect(root.components.length).toBe(0)
|
||||||
expect(root.helpers).toContain(BASE_TRANSITION)
|
expect(root.helpers).toContain(BASE_TRANSITION)
|
||||||
expect(node.callee).toBe(CREATE_VNODE)
|
|
||||||
expect(node.arguments).toMatchObject([
|
expect(node).toMatchObject({
|
||||||
BASE_TRANSITION,
|
tag: BASE_TRANSITION,
|
||||||
`null`,
|
props: undefined,
|
||||||
createObjectMatcher({
|
children: createObjectMatcher({
|
||||||
default: {
|
default: {
|
||||||
type: NodeTypes.JS_FUNCTION_EXPRESSION
|
type: NodeTypes.JS_FUNCTION_EXPRESSION
|
||||||
},
|
},
|
||||||
_compiled: `[true]`
|
_: `[1]`
|
||||||
})
|
})
|
||||||
])
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(`base-transition`)
|
assert(`base-transition`)
|
||||||
@ -398,14 +407,13 @@ describe('compiler: element transform', () => {
|
|||||||
foo(dir) {
|
foo(dir) {
|
||||||
_dir = dir
|
_dir = dir
|
||||||
return {
|
return {
|
||||||
props: [createObjectProperty(dir.arg!, dir.exp!)],
|
props: [createObjectProperty(dir.arg!, dir.exp!)]
|
||||||
needRuntime: false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
expect(node.callee).toBe(CREATE_VNODE)
|
|
||||||
expect(node.arguments[1]).toMatchObject({
|
expect(node.props).toMatchObject({
|
||||||
type: NodeTypes.JS_OBJECT_EXPRESSION,
|
type: NodeTypes.JS_OBJECT_EXPRESSION,
|
||||||
properties: [
|
properties: [
|
||||||
{
|
{
|
||||||
@ -417,8 +425,8 @@ describe('compiler: element transform', () => {
|
|||||||
})
|
})
|
||||||
// should factor in props returned by custom directive transforms
|
// should factor in props returned by custom directive transforms
|
||||||
// in patchFlag analysis
|
// in patchFlag analysis
|
||||||
expect(node.arguments[3]).toMatch(PatchFlags.PROPS + '')
|
expect(node.patchFlag).toMatch(PatchFlags.PROPS + '')
|
||||||
expect(node.arguments[4]).toMatch(`"bar"`)
|
expect(node.dynamicProps).toMatch(`"bar"`)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('directiveTransform with needRuntime: true', () => {
|
test('directiveTransform with needRuntime: true', () => {
|
||||||
@ -437,20 +445,12 @@ describe('compiler: element transform', () => {
|
|||||||
)
|
)
|
||||||
expect(root.helpers).toContain(RESOLVE_DIRECTIVE)
|
expect(root.helpers).toContain(RESOLVE_DIRECTIVE)
|
||||||
expect(root.directives).toContain(`foo`)
|
expect(root.directives).toContain(`foo`)
|
||||||
|
expect(node).toMatchObject({
|
||||||
expect(node.callee).toBe(WITH_DIRECTIVES)
|
tag: `"div"`,
|
||||||
expect(node.arguments).toMatchObject([
|
props: undefined,
|
||||||
{
|
children: undefined,
|
||||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
patchFlag: genFlagText(PatchFlags.NEED_PATCH), // should generate appropriate flag
|
||||||
callee: CREATE_VNODE,
|
directives: {
|
||||||
arguments: [
|
|
||||||
`"div"`,
|
|
||||||
`null`,
|
|
||||||
`null`,
|
|
||||||
genFlagText(PatchFlags.NEED_PATCH) // should generate appropriate flag
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: NodeTypes.JS_ARRAY_EXPRESSION,
|
type: NodeTypes.JS_ARRAY_EXPRESSION,
|
||||||
elements: [
|
elements: [
|
||||||
{
|
{
|
||||||
@ -473,7 +473,7 @@ describe('compiler: element transform', () => {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
])
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test('directiveTransform with needRuntime: Symbol', () => {
|
test('directiveTransform with needRuntime: Symbol', () => {
|
||||||
@ -494,7 +494,7 @@ describe('compiler: element transform', () => {
|
|||||||
expect(root.helpers).toContain(CREATE_VNODE)
|
expect(root.helpers).toContain(CREATE_VNODE)
|
||||||
expect(root.helpers).not.toContain(RESOLVE_DIRECTIVE)
|
expect(root.helpers).not.toContain(RESOLVE_DIRECTIVE)
|
||||||
expect(root.directives.length).toBe(0)
|
expect(root.directives.length).toBe(0)
|
||||||
expect((node as any).arguments[1].elements[0].elements[0]).toBe(
|
expect(node.directives!.elements[0].elements[0]).toBe(
|
||||||
`_${helperNameMap[CREATE_VNODE]}`
|
`_${helperNameMap[CREATE_VNODE]}`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@ -508,12 +508,8 @@ describe('compiler: element transform', () => {
|
|||||||
expect(root.directives).toContain(`bar`)
|
expect(root.directives).toContain(`bar`)
|
||||||
expect(root.directives).toContain(`baz`)
|
expect(root.directives).toContain(`baz`)
|
||||||
|
|
||||||
expect(node.callee).toBe(WITH_DIRECTIVES)
|
expect(node).toMatchObject({
|
||||||
expect(node.arguments).toMatchObject([
|
directives: {
|
||||||
{
|
|
||||||
type: NodeTypes.JS_CALL_EXPRESSION
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: NodeTypes.JS_ARRAY_EXPRESSION,
|
type: NodeTypes.JS_ARRAY_EXPRESSION,
|
||||||
elements: [
|
elements: [
|
||||||
{
|
{
|
||||||
@ -583,7 +579,7 @@ describe('compiler: element transform', () => {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
])
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test(`props merging: event handlers`, () => {
|
test(`props merging: event handlers`, () => {
|
||||||
@ -595,7 +591,7 @@ describe('compiler: element transform', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
expect(node.arguments[1]).toMatchObject({
|
expect(node.props).toMatchObject({
|
||||||
type: NodeTypes.JS_OBJECT_EXPRESSION,
|
type: NodeTypes.JS_OBJECT_EXPRESSION,
|
||||||
properties: [
|
properties: [
|
||||||
{
|
{
|
||||||
@ -627,7 +623,7 @@ describe('compiler: element transform', () => {
|
|||||||
|
|
||||||
test(`props merging: style`, () => {
|
test(`props merging: style`, () => {
|
||||||
const { node } = parseWithElementTransform(
|
const { node } = parseWithElementTransform(
|
||||||
`<div style="color: red" :style="{ color: 'red' }" />`,
|
`<div style="color: green" :style="{ color: 'red' }" />`,
|
||||||
{
|
{
|
||||||
nodeTransforms: [transformStyle, transformElement],
|
nodeTransforms: [transformStyle, transformElement],
|
||||||
directiveTransforms: {
|
directiveTransforms: {
|
||||||
@ -635,7 +631,7 @@ describe('compiler: element transform', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
expect(node.arguments[1]).toMatchObject({
|
expect(node.props).toMatchObject({
|
||||||
type: NodeTypes.JS_OBJECT_EXPRESSION,
|
type: NodeTypes.JS_OBJECT_EXPRESSION,
|
||||||
properties: [
|
properties: [
|
||||||
{
|
{
|
||||||
@ -650,7 +646,7 @@ describe('compiler: element transform', () => {
|
|||||||
elements: [
|
elements: [
|
||||||
{
|
{
|
||||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
content: `_hoisted_1`,
|
content: `{"color":"green"}`,
|
||||||
isStatic: false
|
isStatic: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -674,7 +670,7 @@ describe('compiler: element transform', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
expect(node.arguments[1]).toMatchObject({
|
expect(node.props).toMatchObject({
|
||||||
type: NodeTypes.JS_OBJECT_EXPRESSION,
|
type: NodeTypes.JS_OBJECT_EXPRESSION,
|
||||||
properties: [
|
properties: [
|
||||||
{
|
{
|
||||||
@ -707,113 +703,175 @@ describe('compiler: element transform', () => {
|
|||||||
describe('patchFlag analysis', () => {
|
describe('patchFlag analysis', () => {
|
||||||
test('TEXT', () => {
|
test('TEXT', () => {
|
||||||
const { node } = parseWithBind(`<div>foo</div>`)
|
const { node } = parseWithBind(`<div>foo</div>`)
|
||||||
expect(node.arguments.length).toBe(3)
|
expect(node.patchFlag).toBeUndefined()
|
||||||
|
|
||||||
const { node: node2 } = parseWithBind(`<div>{{ foo }}</div>`)
|
const { node: node2 } = parseWithBind(`<div>{{ foo }}</div>`)
|
||||||
expect(node2.arguments.length).toBe(4)
|
expect(node2.patchFlag).toBe(genFlagText(PatchFlags.TEXT))
|
||||||
expect(node2.arguments[3]).toBe(genFlagText(PatchFlags.TEXT))
|
|
||||||
|
|
||||||
// multiple nodes, merged with optimize text
|
// multiple nodes, merged with optimize text
|
||||||
const { node: node3 } = parseWithBind(`<div>foo {{ bar }} baz</div>`)
|
const { node: node3 } = parseWithBind(`<div>foo {{ bar }} baz</div>`)
|
||||||
expect(node3.arguments.length).toBe(4)
|
expect(node3.patchFlag).toBe(genFlagText(PatchFlags.TEXT))
|
||||||
expect(node3.arguments[3]).toBe(genFlagText(PatchFlags.TEXT))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test('CLASS', () => {
|
test('CLASS', () => {
|
||||||
const { node } = parseWithBind(`<div :class="foo" />`)
|
const { node } = parseWithBind(`<div :class="foo" />`)
|
||||||
expect(node.arguments.length).toBe(4)
|
expect(node.patchFlag).toBe(genFlagText(PatchFlags.CLASS))
|
||||||
expect(node.arguments[3]).toBe(genFlagText(PatchFlags.CLASS))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test('STYLE', () => {
|
test('STYLE', () => {
|
||||||
const { node } = parseWithBind(`<div :style="foo" />`)
|
const { node } = parseWithBind(`<div :style="foo" />`)
|
||||||
expect(node.arguments.length).toBe(4)
|
expect(node.patchFlag).toBe(genFlagText(PatchFlags.STYLE))
|
||||||
expect(node.arguments[3]).toBe(genFlagText(PatchFlags.STYLE))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test('PROPS', () => {
|
test('PROPS', () => {
|
||||||
const { node } = parseWithBind(`<div id="foo" :foo="bar" :baz="qux" />`)
|
const { node } = parseWithBind(`<div id="foo" :foo="bar" :baz="qux" />`)
|
||||||
expect(node.arguments.length).toBe(5)
|
expect(node.patchFlag).toBe(genFlagText(PatchFlags.PROPS))
|
||||||
expect(node.arguments[3]).toBe(genFlagText(PatchFlags.PROPS))
|
expect(node.dynamicProps).toBe(`["foo", "baz"]`)
|
||||||
expect(node.arguments[4]).toBe(`["foo", "baz"]`)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test('CLASS + STYLE + PROPS', () => {
|
test('CLASS + STYLE + PROPS', () => {
|
||||||
const { node } = parseWithBind(
|
const { node } = parseWithBind(
|
||||||
`<div id="foo" :class="cls" :style="styl" :foo="bar" :baz="qux"/>`
|
`<div id="foo" :class="cls" :style="styl" :foo="bar" :baz="qux"/>`
|
||||||
)
|
)
|
||||||
expect(node.arguments.length).toBe(5)
|
expect(node.patchFlag).toBe(
|
||||||
expect(node.arguments[3]).toBe(
|
|
||||||
genFlagText([PatchFlags.CLASS, PatchFlags.STYLE, PatchFlags.PROPS])
|
genFlagText([PatchFlags.CLASS, PatchFlags.STYLE, PatchFlags.PROPS])
|
||||||
)
|
)
|
||||||
expect(node.arguments[4]).toBe(`["foo", "baz"]`)
|
expect(node.dynamicProps).toBe(`["foo", "baz"]`)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('FULL_PROPS (v-bind)', () => {
|
test('FULL_PROPS (v-bind)', () => {
|
||||||
const { node } = parseWithBind(`<div v-bind="foo" />`)
|
const { node } = parseWithBind(`<div v-bind="foo" />`)
|
||||||
expect(node.arguments.length).toBe(4)
|
expect(node.patchFlag).toBe(genFlagText(PatchFlags.FULL_PROPS))
|
||||||
expect(node.arguments[3]).toBe(genFlagText(PatchFlags.FULL_PROPS))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test('FULL_PROPS (dynamic key)', () => {
|
test('FULL_PROPS (dynamic key)', () => {
|
||||||
const { node } = parseWithBind(`<div :[foo]="bar" />`)
|
const { node } = parseWithBind(`<div :[foo]="bar" />`)
|
||||||
expect(node.arguments.length).toBe(4)
|
expect(node.patchFlag).toBe(genFlagText(PatchFlags.FULL_PROPS))
|
||||||
expect(node.arguments[3]).toBe(genFlagText(PatchFlags.FULL_PROPS))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test('FULL_PROPS (w/ others)', () => {
|
test('FULL_PROPS (w/ others)', () => {
|
||||||
const { node } = parseWithBind(
|
const { node } = parseWithBind(
|
||||||
`<div id="foo" v-bind="bar" :class="cls" />`
|
`<div id="foo" v-bind="bar" :class="cls" />`
|
||||||
)
|
)
|
||||||
expect(node.arguments.length).toBe(4)
|
expect(node.patchFlag).toBe(genFlagText(PatchFlags.FULL_PROPS))
|
||||||
expect(node.arguments[3]).toBe(genFlagText(PatchFlags.FULL_PROPS))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test('NEED_PATCH (static ref)', () => {
|
test('NEED_PATCH (static ref)', () => {
|
||||||
const { node } = parseWithBind(`<div ref="foo" />`)
|
const { node } = parseWithBind(`<div ref="foo" />`)
|
||||||
expect(node.arguments.length).toBe(4)
|
expect(node.patchFlag).toBe(genFlagText(PatchFlags.NEED_PATCH))
|
||||||
expect(node.arguments[3]).toBe(genFlagText(PatchFlags.NEED_PATCH))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test('NEED_PATCH (dynamic ref)', () => {
|
test('NEED_PATCH (dynamic ref)', () => {
|
||||||
const { node } = parseWithBind(`<div :ref="foo" />`)
|
const { node } = parseWithBind(`<div :ref="foo" />`)
|
||||||
expect(node.arguments.length).toBe(4)
|
expect(node.patchFlag).toBe(genFlagText(PatchFlags.NEED_PATCH))
|
||||||
expect(node.arguments[3]).toBe(genFlagText(PatchFlags.NEED_PATCH))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test('NEED_PATCH (custom directives)', () => {
|
test('NEED_PATCH (custom directives)', () => {
|
||||||
const { node } = parseWithBind(`<div v-foo />`)
|
const { node } = parseWithBind(`<div v-foo />`)
|
||||||
const vnodeCall = node.arguments[0] as CallExpression
|
expect(node.patchFlag).toBe(genFlagText(PatchFlags.NEED_PATCH))
|
||||||
expect(vnodeCall.arguments.length).toBe(4)
|
})
|
||||||
expect(vnodeCall.arguments[3]).toBe(genFlagText(PatchFlags.NEED_PATCH))
|
|
||||||
|
test('HYDRATE_EVENTS', () => {
|
||||||
|
// ignore click events (has dedicated fast path)
|
||||||
|
const { node } = parseWithElementTransform(`<div @click="foo" />`, {
|
||||||
|
directiveTransforms: {
|
||||||
|
on: transformOn
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// should only have props flag
|
||||||
|
expect(node.patchFlag).toBe(genFlagText(PatchFlags.PROPS))
|
||||||
|
|
||||||
|
const { node: node2 } = parseWithElementTransform(
|
||||||
|
`<div @keyup="foo" />`,
|
||||||
|
{
|
||||||
|
directiveTransforms: {
|
||||||
|
on: transformOn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
expect(node2.patchFlag).toBe(
|
||||||
|
genFlagText([PatchFlags.PROPS, PatchFlags.HYDRATE_EVENTS])
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('dynamic component', () => {
|
describe('dynamic component', () => {
|
||||||
test('static binding', () => {
|
test('static binding', () => {
|
||||||
const { node, root } = parseWithBind(`<component is="foo" />`)
|
const { node, root } = parseWithBind(`<component is="foo" />`)
|
||||||
expect(root.helpers).not.toContain(RESOLVE_DYNAMIC_COMPONENT)
|
expect(root.helpers).toContain(RESOLVE_DYNAMIC_COMPONENT)
|
||||||
expect(node).toMatchObject({
|
expect(node).toMatchObject({
|
||||||
callee: CREATE_VNODE,
|
tag: {
|
||||||
arguments: ['_component_foo']
|
callee: RESOLVE_DYNAMIC_COMPONENT,
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
|
content: 'foo',
|
||||||
|
isStatic: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test('dynamic binding', () => {
|
test('dynamic binding', () => {
|
||||||
const { node, root } = parseWithBind(`<component :is="foo" />`)
|
const { node, root } = parseWithBind(`<component :is="foo" />`)
|
||||||
expect(root.helpers).toContain(RESOLVE_DYNAMIC_COMPONENT)
|
expect(root.helpers).toContain(RESOLVE_DYNAMIC_COMPONENT)
|
||||||
expect(node.arguments).toMatchObject([
|
expect(node).toMatchObject({
|
||||||
{
|
tag: {
|
||||||
callee: RESOLVE_DYNAMIC_COMPONENT,
|
callee: RESOLVE_DYNAMIC_COMPONENT,
|
||||||
arguments: [
|
arguments: [
|
||||||
{
|
{
|
||||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
content: 'foo'
|
content: 'foo',
|
||||||
},
|
isStatic: false
|
||||||
'$'
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
])
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('v-is', () => {
|
||||||
|
const { node, root } = parseWithBind(`<div v-is="'foo'" />`)
|
||||||
|
expect(root.helpers).toContain(RESOLVE_DYNAMIC_COMPONENT)
|
||||||
|
expect(node).toMatchObject({
|
||||||
|
tag: {
|
||||||
|
callee: RESOLVE_DYNAMIC_COMPONENT,
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
|
content: `'foo'`,
|
||||||
|
isStatic: false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
// should skip v-is runtime check
|
||||||
|
directives: undefined
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('<svg> should be forced into blocks', () => {
|
||||||
|
const ast = parse(`<div><svg/></div>`)
|
||||||
|
transform(ast, {
|
||||||
|
nodeTransforms: [transformElement]
|
||||||
|
})
|
||||||
|
expect((ast as any).children[0].children[0].codegenNode).toMatchObject({
|
||||||
|
type: NodeTypes.VNODE_CALL,
|
||||||
|
tag: `"svg"`,
|
||||||
|
isBlock: true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// #938
|
||||||
|
test('element with dynamic keys should be forced into blocks', () => {
|
||||||
|
const ast = parse(`<div><div :key="foo" /></div>`)
|
||||||
|
transform(ast, {
|
||||||
|
nodeTransforms: [transformElement]
|
||||||
|
})
|
||||||
|
expect((ast as any).children[0].children[0].codegenNode).toMatchObject({
|
||||||
|
type: NodeTypes.VNODE_CALL,
|
||||||
|
tag: `"div"`,
|
||||||
|
isBlock: true
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
parse,
|
baseParse as parse,
|
||||||
transform,
|
transform,
|
||||||
ElementNode,
|
ElementNode,
|
||||||
DirectiveNode,
|
DirectiveNode,
|
||||||
@ -317,6 +317,16 @@ describe('compiler: expression transform', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('should not duplicate object key with same name as value', () => {
|
||||||
|
const node = parseWithExpressionTransform(
|
||||||
|
`{{ { foo: foo } }}`
|
||||||
|
) as InterpolationNode
|
||||||
|
expect(node.content).toMatchObject({
|
||||||
|
type: NodeTypes.COMPOUND_EXPRESSION,
|
||||||
|
children: [`{ foo: `, { content: `_ctx.foo` }, ` }`]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
test('should prefix a computed object property key', () => {
|
test('should prefix a computed object property key', () => {
|
||||||
const node = parseWithExpressionTransform(
|
const node = parseWithExpressionTransform(
|
||||||
`{{ { [foo]: bar } }}`
|
`{{ { [foo]: bar } }}`
|
||||||
@ -380,7 +390,65 @@ describe('compiler: expression transform', () => {
|
|||||||
const onError = jest.fn()
|
const onError = jest.fn()
|
||||||
parseWithExpressionTransform(`{{ a( }}`, { onError })
|
parseWithExpressionTransform(`{{ a( }}`, { onError })
|
||||||
expect(onError.mock.calls[0][0].message).toMatch(
|
expect(onError.mock.calls[0][0].message).toMatch(
|
||||||
`Invalid JavaScript expression. (1:4)`
|
`Error parsing JavaScript expression: Unexpected token`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('ES Proposals support', () => {
|
||||||
|
test('bigInt', () => {
|
||||||
|
const node = parseWithExpressionTransform(
|
||||||
|
`{{ 13000n }}`
|
||||||
|
) as InterpolationNode
|
||||||
|
expect(node.content).toMatchObject({
|
||||||
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
|
content: `13000n`,
|
||||||
|
isStatic: false,
|
||||||
|
isConstant: true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('nullish colescing', () => {
|
||||||
|
const node = parseWithExpressionTransform(
|
||||||
|
`{{ a ?? b }}`
|
||||||
|
) as InterpolationNode
|
||||||
|
expect(node.content).toMatchObject({
|
||||||
|
type: NodeTypes.COMPOUND_EXPRESSION,
|
||||||
|
children: [{ content: `_ctx.a` }, ` ?? `, { content: `_ctx.b` }]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('optional chaining', () => {
|
||||||
|
const node = parseWithExpressionTransform(
|
||||||
|
`{{ a?.b?.c }}`
|
||||||
|
) as InterpolationNode
|
||||||
|
expect(node.content).toMatchObject({
|
||||||
|
type: NodeTypes.COMPOUND_EXPRESSION,
|
||||||
|
children: [
|
||||||
|
{ content: `_ctx.a` },
|
||||||
|
`?.`,
|
||||||
|
{ content: `b` },
|
||||||
|
`?.`,
|
||||||
|
{ content: `c` }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Enabling additional plugins', () => {
|
||||||
|
// enabling pipeline operator to replace filters:
|
||||||
|
const node = parseWithExpressionTransform(`{{ a |> uppercase }}`, {
|
||||||
|
expressionPlugins: [
|
||||||
|
[
|
||||||
|
'pipelineOperator',
|
||||||
|
{
|
||||||
|
proposal: 'minimal'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}) as InterpolationNode
|
||||||
|
expect(node.content).toMatchObject({
|
||||||
|
type: NodeTypes.COMPOUND_EXPRESSION,
|
||||||
|
children: [{ content: `_ctx.a` }, ` |> `, { content: `_ctx.uppercase` }]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
CompilerOptions,
|
CompilerOptions,
|
||||||
parse,
|
baseParse as parse,
|
||||||
transform,
|
transform,
|
||||||
ElementNode,
|
ElementNode,
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
CompilerOptions,
|
CompilerOptions,
|
||||||
parse,
|
baseParse as parse,
|
||||||
transform,
|
transform,
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
generate,
|
generate,
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import {
|
import {
|
||||||
parse,
|
baseParse as parse,
|
||||||
transform,
|
transform,
|
||||||
ElementNode,
|
ElementNode,
|
||||||
ObjectExpression,
|
ObjectExpression,
|
||||||
CompilerOptions,
|
CompilerOptions,
|
||||||
ErrorCodes,
|
ErrorCodes,
|
||||||
CallExpression
|
VNodeCall
|
||||||
} from '../../src'
|
} from '../../src'
|
||||||
import { transformBind } from '../../src/transforms/vBind'
|
import { transformBind } from '../../src/transforms/vBind'
|
||||||
import { transformElement } from '../../src/transforms/transformElement'
|
import { transformElement } from '../../src/transforms/transformElement'
|
||||||
@ -33,8 +33,7 @@ function parseWithVBind(
|
|||||||
describe('compiler: transform v-bind', () => {
|
describe('compiler: transform v-bind', () => {
|
||||||
test('basic', () => {
|
test('basic', () => {
|
||||||
const node = parseWithVBind(`<div v-bind:id="id"/>`)
|
const node = parseWithVBind(`<div v-bind:id="id"/>`)
|
||||||
const props = (node.codegenNode as CallExpression)
|
const props = (node.codegenNode as VNodeCall).props as ObjectExpression
|
||||||
.arguments[1] as ObjectExpression
|
|
||||||
expect(props.properties[0]).toMatchObject({
|
expect(props.properties[0]).toMatchObject({
|
||||||
key: {
|
key: {
|
||||||
content: `id`,
|
content: `id`,
|
||||||
@ -69,8 +68,7 @@ describe('compiler: transform v-bind', () => {
|
|||||||
|
|
||||||
test('dynamic arg', () => {
|
test('dynamic arg', () => {
|
||||||
const node = parseWithVBind(`<div v-bind:[id]="id"/>`)
|
const node = parseWithVBind(`<div v-bind:[id]="id"/>`)
|
||||||
const props = (node.codegenNode as CallExpression)
|
const props = (node.codegenNode as VNodeCall).props as ObjectExpression
|
||||||
.arguments[1] as ObjectExpression
|
|
||||||
expect(props.properties[0]).toMatchObject({
|
expect(props.properties[0]).toMatchObject({
|
||||||
key: {
|
key: {
|
||||||
content: `id`,
|
content: `id`,
|
||||||
@ -103,8 +101,7 @@ describe('compiler: transform v-bind', () => {
|
|||||||
|
|
||||||
test('.camel modifier', () => {
|
test('.camel modifier', () => {
|
||||||
const node = parseWithVBind(`<div v-bind:foo-bar.camel="id"/>`)
|
const node = parseWithVBind(`<div v-bind:foo-bar.camel="id"/>`)
|
||||||
const props = (node.codegenNode as CallExpression)
|
const props = (node.codegenNode as VNodeCall).props as ObjectExpression
|
||||||
.arguments[1] as ObjectExpression
|
|
||||||
expect(props.properties[0]).toMatchObject({
|
expect(props.properties[0]).toMatchObject({
|
||||||
key: {
|
key: {
|
||||||
content: `fooBar`,
|
content: `fooBar`,
|
||||||
@ -119,8 +116,7 @@ describe('compiler: transform v-bind', () => {
|
|||||||
|
|
||||||
test('.camel modifier w/ dynamic arg', () => {
|
test('.camel modifier w/ dynamic arg', () => {
|
||||||
const node = parseWithVBind(`<div v-bind:[foo].camel="id"/>`)
|
const node = parseWithVBind(`<div v-bind:[foo].camel="id"/>`)
|
||||||
const props = (node.codegenNode as CallExpression)
|
const props = (node.codegenNode as VNodeCall).props as ObjectExpression
|
||||||
.arguments[1] as ObjectExpression
|
|
||||||
expect(props.properties[0]).toMatchObject({
|
expect(props.properties[0]).toMatchObject({
|
||||||
key: {
|
key: {
|
||||||
content: `_${helperNameMap[CAMELIZE]}(foo)`,
|
content: `_${helperNameMap[CAMELIZE]}(foo)`,
|
||||||
@ -137,12 +133,11 @@ describe('compiler: transform v-bind', () => {
|
|||||||
const node = parseWithVBind(`<div v-bind:[foo(bar)].camel="id"/>`, {
|
const node = parseWithVBind(`<div v-bind:[foo(bar)].camel="id"/>`, {
|
||||||
prefixIdentifiers: true
|
prefixIdentifiers: true
|
||||||
})
|
})
|
||||||
const props = (node.codegenNode as CallExpression)
|
const props = (node.codegenNode as VNodeCall).props as ObjectExpression
|
||||||
.arguments[1] as ObjectExpression
|
|
||||||
expect(props.properties[0]).toMatchObject({
|
expect(props.properties[0]).toMatchObject({
|
||||||
key: {
|
key: {
|
||||||
children: [
|
children: [
|
||||||
`${helperNameMap[CAMELIZE]}(`,
|
`_${helperNameMap[CAMELIZE]}(`,
|
||||||
{ content: `_ctx.foo` },
|
{ content: `_ctx.foo` },
|
||||||
`(`,
|
`(`,
|
||||||
{ content: `_ctx.bar` },
|
{ content: `_ctx.bar` },
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { parse } from '../../src/parse'
|
import { baseParse as parse } from '../../src/parse'
|
||||||
import { transform } from '../../src/transform'
|
import { transform } from '../../src/transform'
|
||||||
import { transformIf } from '../../src/transforms/vIf'
|
import { transformIf } from '../../src/transforms/vIf'
|
||||||
import { transformFor } from '../../src/transforms/vFor'
|
import { transformFor } from '../../src/transforms/vFor'
|
||||||
@ -12,19 +12,11 @@ import {
|
|||||||
SimpleExpressionNode,
|
SimpleExpressionNode,
|
||||||
ElementNode,
|
ElementNode,
|
||||||
InterpolationNode,
|
InterpolationNode,
|
||||||
CallExpression,
|
ForCodegenNode
|
||||||
SequenceExpression
|
|
||||||
} from '../../src/ast'
|
} from '../../src/ast'
|
||||||
import { ErrorCodes } from '../../src/errors'
|
import { ErrorCodes } from '../../src/errors'
|
||||||
import { CompilerOptions, generate } from '../../src'
|
import { CompilerOptions, generate } from '../../src'
|
||||||
import {
|
import { FRAGMENT, RENDER_LIST, RENDER_SLOT } from '../../src/runtimeHelpers'
|
||||||
OPEN_BLOCK,
|
|
||||||
CREATE_BLOCK,
|
|
||||||
FRAGMENT,
|
|
||||||
RENDER_LIST,
|
|
||||||
RENDER_SLOT,
|
|
||||||
WITH_DIRECTIVES
|
|
||||||
} from '../../src/runtimeHelpers'
|
|
||||||
import { PatchFlags } from '@vue/shared'
|
import { PatchFlags } from '@vue/shared'
|
||||||
import { createObjectMatcher, genFlagText } from '../testUtils'
|
import { createObjectMatcher, genFlagText } from '../testUtils'
|
||||||
|
|
||||||
@ -48,7 +40,7 @@ function parseWithForTransform(
|
|||||||
})
|
})
|
||||||
return {
|
return {
|
||||||
root: ast,
|
root: ast,
|
||||||
node: ast.children[0] as ForNode
|
node: ast.children[0] as ForNode & { codegenNode: ForCodegenNode }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -566,64 +558,40 @@ describe('compiler: v-for', () => {
|
|||||||
|
|
||||||
describe('codegen', () => {
|
describe('codegen', () => {
|
||||||
function assertSharedCodegen(
|
function assertSharedCodegen(
|
||||||
node: SequenceExpression,
|
node: ForCodegenNode,
|
||||||
keyed: boolean = false,
|
keyed: boolean = false,
|
||||||
customReturn: boolean = false
|
customReturn: boolean = false
|
||||||
) {
|
) {
|
||||||
expect(node).toMatchObject({
|
expect(node).toMatchObject({
|
||||||
type: NodeTypes.JS_SEQUENCE_EXPRESSION,
|
type: NodeTypes.VNODE_CALL,
|
||||||
expressions: [
|
tag: FRAGMENT,
|
||||||
{
|
isForBlock: true,
|
||||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
patchFlag: keyed
|
||||||
callee: OPEN_BLOCK
|
? genFlagText(PatchFlags.KEYED_FRAGMENT)
|
||||||
},
|
: genFlagText(PatchFlags.UNKEYED_FRAGMENT),
|
||||||
{
|
children: {
|
||||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||||
callee: CREATE_BLOCK,
|
callee: RENDER_LIST,
|
||||||
arguments: [
|
arguments: [
|
||||||
FRAGMENT,
|
{}, // to be asserted by each test
|
||||||
`null`,
|
{
|
||||||
{
|
type: NodeTypes.JS_FUNCTION_EXPRESSION,
|
||||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
returns: customReturn
|
||||||
callee: RENDER_LIST,
|
? {}
|
||||||
arguments: [
|
: {
|
||||||
{}, // to be asserted by each test
|
type: NodeTypes.VNODE_CALL,
|
||||||
{
|
isBlock: true
|
||||||
type: NodeTypes.JS_FUNCTION_EXPRESSION,
|
|
||||||
returns: customReturn
|
|
||||||
? {}
|
|
||||||
: {
|
|
||||||
type: NodeTypes.JS_SEQUENCE_EXPRESSION,
|
|
||||||
expressions: [
|
|
||||||
{
|
|
||||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
|
||||||
callee: OPEN_BLOCK
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
|
||||||
callee: CREATE_BLOCK
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
]
|
||||||
keyed
|
}
|
||||||
? genFlagText(PatchFlags.KEYED_FRAGMENT)
|
|
||||||
: genFlagText(PatchFlags.UNKEYED_FRAGMENT)
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
})
|
||||||
const renderListArgs = ((node.expressions[1] as CallExpression)
|
const renderListArgs = node.children.arguments
|
||||||
.arguments[2] as CallExpression).arguments
|
|
||||||
return {
|
return {
|
||||||
source: renderListArgs[0] as SimpleExpressionNode,
|
source: renderListArgs[0] as SimpleExpressionNode,
|
||||||
params: (renderListArgs[1] as any).params,
|
params: (renderListArgs[1] as any).params,
|
||||||
returns: (renderListArgs[1] as any).returns,
|
returns: (renderListArgs[1] as any).returns,
|
||||||
blockArgs: customReturn
|
innerVNodeCall: customReturn ? null : (renderListArgs[1] as any).returns
|
||||||
? null
|
|
||||||
: (renderListArgs[1] as any).returns.expressions[1].arguments
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -635,7 +603,9 @@ describe('compiler: v-for', () => {
|
|||||||
expect(assertSharedCodegen(codegenNode)).toMatchObject({
|
expect(assertSharedCodegen(codegenNode)).toMatchObject({
|
||||||
source: { content: `items` },
|
source: { content: `items` },
|
||||||
params: [{ content: `item` }],
|
params: [{ content: `item` }],
|
||||||
blockArgs: [`"span"`]
|
innerVNodeCall: {
|
||||||
|
tag: `"span"`
|
||||||
|
}
|
||||||
})
|
})
|
||||||
expect(generate(root).code).toMatchSnapshot()
|
expect(generate(root).code).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
@ -698,15 +668,16 @@ describe('compiler: v-for', () => {
|
|||||||
expect(assertSharedCodegen(codegenNode)).toMatchObject({
|
expect(assertSharedCodegen(codegenNode)).toMatchObject({
|
||||||
source: { content: `items` },
|
source: { content: `items` },
|
||||||
params: [{ content: `item` }],
|
params: [{ content: `item` }],
|
||||||
blockArgs: [
|
innerVNodeCall: {
|
||||||
FRAGMENT,
|
tag: FRAGMENT,
|
||||||
`null`,
|
props: undefined,
|
||||||
[
|
isBlock: true,
|
||||||
|
children: [
|
||||||
{ type: NodeTypes.TEXT, content: `hello` },
|
{ type: NodeTypes.TEXT, content: `hello` },
|
||||||
{ type: NodeTypes.ELEMENT, tag: `span` }
|
{ type: NodeTypes.ELEMENT, tag: `span` }
|
||||||
],
|
],
|
||||||
genFlagText(PatchFlags.STABLE_FRAGMENT)
|
patchFlag: genFlagText(PatchFlags.STABLE_FRAGMENT)
|
||||||
]
|
}
|
||||||
})
|
})
|
||||||
expect(generate(root).code).toMatchSnapshot()
|
expect(generate(root).code).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
@ -757,12 +728,12 @@ describe('compiler: v-for', () => {
|
|||||||
expect(assertSharedCodegen(codegenNode, true)).toMatchObject({
|
expect(assertSharedCodegen(codegenNode, true)).toMatchObject({
|
||||||
source: { content: `items` },
|
source: { content: `items` },
|
||||||
params: [{ content: `item` }],
|
params: [{ content: `item` }],
|
||||||
blockArgs: [
|
innerVNodeCall: {
|
||||||
`"span"`,
|
tag: `"span"`,
|
||||||
createObjectMatcher({
|
props: createObjectMatcher({
|
||||||
key: `[item]`
|
key: `[item]`
|
||||||
})
|
})
|
||||||
]
|
}
|
||||||
})
|
})
|
||||||
expect(generate(root).code).toMatchSnapshot()
|
expect(generate(root).code).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
@ -777,17 +748,17 @@ describe('compiler: v-for', () => {
|
|||||||
expect(assertSharedCodegen(codegenNode, true)).toMatchObject({
|
expect(assertSharedCodegen(codegenNode, true)).toMatchObject({
|
||||||
source: { content: `items` },
|
source: { content: `items` },
|
||||||
params: [{ content: `item` }],
|
params: [{ content: `item` }],
|
||||||
blockArgs: [
|
innerVNodeCall: {
|
||||||
FRAGMENT,
|
tag: FRAGMENT,
|
||||||
createObjectMatcher({
|
props: createObjectMatcher({
|
||||||
key: `[item]`
|
key: `[item]`
|
||||||
}),
|
}),
|
||||||
[
|
children: [
|
||||||
{ type: NodeTypes.TEXT, content: `hello` },
|
{ type: NodeTypes.TEXT, content: `hello` },
|
||||||
{ type: NodeTypes.ELEMENT, tag: `span` }
|
{ type: NodeTypes.ELEMENT, tag: `span` }
|
||||||
],
|
],
|
||||||
genFlagText(PatchFlags.STABLE_FRAGMENT)
|
patchFlag: genFlagText(PatchFlags.STABLE_FRAGMENT)
|
||||||
]
|
}
|
||||||
})
|
})
|
||||||
expect(generate(root).code).toMatchSnapshot()
|
expect(generate(root).code).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
@ -798,53 +769,33 @@ describe('compiler: v-for', () => {
|
|||||||
node: { codegenNode }
|
node: { codegenNode }
|
||||||
} = parseWithForTransform(`<div v-if="ok" v-for="i in list"/>`)
|
} = parseWithForTransform(`<div v-if="ok" v-for="i in list"/>`)
|
||||||
expect(codegenNode).toMatchObject({
|
expect(codegenNode).toMatchObject({
|
||||||
type: NodeTypes.JS_SEQUENCE_EXPRESSION,
|
type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
|
||||||
expressions: [
|
test: { content: `ok` },
|
||||||
{
|
consequent: {
|
||||||
|
type: NodeTypes.VNODE_CALL,
|
||||||
|
props: createObjectMatcher({
|
||||||
|
key: `[0]`
|
||||||
|
}),
|
||||||
|
isBlock: true,
|
||||||
|
isForBlock: true,
|
||||||
|
patchFlag: genFlagText(PatchFlags.UNKEYED_FRAGMENT),
|
||||||
|
children: {
|
||||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||||
callee: OPEN_BLOCK,
|
callee: RENDER_LIST,
|
||||||
arguments: []
|
arguments: [
|
||||||
},
|
{ content: `list` },
|
||||||
{
|
{
|
||||||
type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
|
type: NodeTypes.JS_FUNCTION_EXPRESSION,
|
||||||
test: { content: `ok` },
|
params: [{ content: `i` }],
|
||||||
consequent: {
|
returns: {
|
||||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
type: NodeTypes.VNODE_CALL,
|
||||||
callee: CREATE_BLOCK,
|
tag: `"div"`,
|
||||||
// should optimize v-if + v-for into a single Fragment block
|
isBlock: true
|
||||||
arguments: [
|
}
|
||||||
FRAGMENT,
|
}
|
||||||
createObjectMatcher({ key: `[0]` }),
|
]
|
||||||
{
|
|
||||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
|
||||||
callee: RENDER_LIST,
|
|
||||||
arguments: [
|
|
||||||
{ content: `list` },
|
|
||||||
{
|
|
||||||
type: NodeTypes.JS_FUNCTION_EXPRESSION,
|
|
||||||
params: [{ content: `i` }],
|
|
||||||
returns: {
|
|
||||||
type: NodeTypes.JS_SEQUENCE_EXPRESSION,
|
|
||||||
expressions: [
|
|
||||||
{
|
|
||||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
|
||||||
callee: OPEN_BLOCK
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
|
||||||
callee: CREATE_BLOCK,
|
|
||||||
arguments: [`"div"`]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
genFlagText(PatchFlags.UNKEYED_FRAGMENT)
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
})
|
})
|
||||||
expect(generate(root).code).toMatchSnapshot()
|
expect(generate(root).code).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
@ -856,18 +807,8 @@ describe('compiler: v-for', () => {
|
|||||||
} = parseWithForTransform('<div v-for="i in list" v-foo/>')
|
} = parseWithForTransform('<div v-for="i in list" v-foo/>')
|
||||||
const { returns } = assertSharedCodegen(codegenNode, false, true)
|
const { returns } = assertSharedCodegen(codegenNode, false, true)
|
||||||
expect(returns).toMatchObject({
|
expect(returns).toMatchObject({
|
||||||
type: NodeTypes.JS_SEQUENCE_EXPRESSION,
|
type: NodeTypes.VNODE_CALL,
|
||||||
expressions: [
|
directives: { type: NodeTypes.JS_ARRAY_EXPRESSION }
|
||||||
{ callee: OPEN_BLOCK },
|
|
||||||
// should wrap withDirectives() around createBlock()
|
|
||||||
{
|
|
||||||
callee: WITH_DIRECTIVES,
|
|
||||||
arguments: [
|
|
||||||
{ callee: CREATE_BLOCK },
|
|
||||||
{ type: NodeTypes.JS_ARRAY_EXPRESSION }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
})
|
||||||
expect(generate(root).code).toMatchSnapshot()
|
expect(generate(root).code).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { parse } from '../../src/parse'
|
import { baseParse as parse } from '../../src/parse'
|
||||||
import { transform } from '../../src/transform'
|
import { transform } from '../../src/transform'
|
||||||
import { transformIf } from '../../src/transforms/vIf'
|
import { transformIf } from '../../src/transforms/vIf'
|
||||||
import { transformElement } from '../../src/transforms/transformElement'
|
import { transformElement } from '../../src/transforms/transformElement'
|
||||||
@ -10,18 +10,16 @@ import {
|
|||||||
TextNode,
|
TextNode,
|
||||||
CommentNode,
|
CommentNode,
|
||||||
SimpleExpressionNode,
|
SimpleExpressionNode,
|
||||||
SequenceExpression,
|
|
||||||
ConditionalExpression,
|
ConditionalExpression,
|
||||||
CallExpression
|
IfConditionalExpression,
|
||||||
|
VNodeCall,
|
||||||
|
ElementTypes
|
||||||
} from '../../src/ast'
|
} from '../../src/ast'
|
||||||
import { ErrorCodes } from '../../src/errors'
|
import { ErrorCodes } from '../../src/errors'
|
||||||
import { CompilerOptions, generate } from '../../src'
|
import { CompilerOptions, generate } from '../../src'
|
||||||
import {
|
import {
|
||||||
OPEN_BLOCK,
|
|
||||||
CREATE_BLOCK,
|
|
||||||
FRAGMENT,
|
FRAGMENT,
|
||||||
MERGE_PROPS,
|
MERGE_PROPS,
|
||||||
WITH_DIRECTIVES,
|
|
||||||
RENDER_SLOT,
|
RENDER_SLOT,
|
||||||
CREATE_COMMENT
|
CREATE_COMMENT
|
||||||
} from '../../src/runtimeHelpers'
|
} from '../../src/runtimeHelpers'
|
||||||
@ -43,7 +41,9 @@ function parseWithIfTransform(
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
root: ast,
|
root: ast,
|
||||||
node: ast.children[returnIndex] as IfNode
|
node: ast.children[returnIndex] as IfNode & {
|
||||||
|
codegenNode: IfConditionalExpression
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,6 +79,22 @@ describe('compiler: v-if', () => {
|
|||||||
expect((node.branches[0].children[2] as ElementNode).tag).toBe(`p`)
|
expect((node.branches[0].children[2] as ElementNode).tag).toBe(`p`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('component v-if', () => {
|
||||||
|
const { node } = parseWithIfTransform(`<Component v-if="ok"></Component>`)
|
||||||
|
expect(node.type).toBe(NodeTypes.IF)
|
||||||
|
expect(node.branches.length).toBe(1)
|
||||||
|
expect((node.branches[0].children[0] as ElementNode).tag).toBe(
|
||||||
|
`Component`
|
||||||
|
)
|
||||||
|
expect((node.branches[0].children[0] as ElementNode).tagType).toBe(
|
||||||
|
ElementTypes.COMPONENT
|
||||||
|
)
|
||||||
|
expect(
|
||||||
|
((node.branches[0].children[0] as ElementNode)!
|
||||||
|
.codegenNode as VNodeCall)!.isBlock
|
||||||
|
).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
test('v-if + v-else', () => {
|
test('v-if + v-else', () => {
|
||||||
const { node } = parseWithIfTransform(`<div v-if="ok"/><p v-else/>`)
|
const { node } = parseWithIfTransform(`<div v-if="ok"/><p v-else/>`)
|
||||||
expect(node.type).toBe(NodeTypes.IF)
|
expect(node.type).toBe(NodeTypes.IF)
|
||||||
@ -266,49 +282,49 @@ describe('compiler: v-if', () => {
|
|||||||
|
|
||||||
describe('codegen', () => {
|
describe('codegen', () => {
|
||||||
function assertSharedCodegen(
|
function assertSharedCodegen(
|
||||||
node: SequenceExpression,
|
node: IfConditionalExpression,
|
||||||
depth: number = 0,
|
depth: number = 0,
|
||||||
hasElse: boolean = false
|
hasElse: boolean = false
|
||||||
) {
|
) {
|
||||||
expect(node).toMatchObject({
|
expect(node).toMatchObject({
|
||||||
type: NodeTypes.JS_SEQUENCE_EXPRESSION,
|
type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
|
||||||
expressions: [
|
test: {
|
||||||
{
|
content: `ok`
|
||||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
},
|
||||||
callee: OPEN_BLOCK,
|
consequent: {
|
||||||
arguments: []
|
type: NodeTypes.VNODE_CALL,
|
||||||
},
|
isBlock: true
|
||||||
{
|
},
|
||||||
type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
|
alternate:
|
||||||
test: {
|
depth < 1
|
||||||
content: `ok`
|
? hasElse
|
||||||
},
|
? {
|
||||||
consequent: {
|
type: NodeTypes.VNODE_CALL,
|
||||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
isBlock: true
|
||||||
callee: CREATE_BLOCK
|
}
|
||||||
},
|
: {
|
||||||
alternate:
|
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||||
depth < 1
|
callee: CREATE_COMMENT
|
||||||
? {
|
}
|
||||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
: {
|
||||||
callee: hasElse ? CREATE_BLOCK : CREATE_COMMENT
|
type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
|
||||||
}
|
test: {
|
||||||
: {
|
content: `orNot`
|
||||||
type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
|
},
|
||||||
test: {
|
consequent: {
|
||||||
content: `orNot`
|
type: NodeTypes.VNODE_CALL,
|
||||||
},
|
isBlock: true
|
||||||
consequent: {
|
},
|
||||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
alternate: hasElse
|
||||||
callee: CREATE_BLOCK
|
? {
|
||||||
},
|
type: NodeTypes.VNODE_CALL,
|
||||||
alternate: {
|
isBlock: true
|
||||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
|
||||||
callee: hasElse ? CREATE_BLOCK : CREATE_COMMENT
|
|
||||||
}
|
}
|
||||||
}
|
: {
|
||||||
}
|
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||||
]
|
callee: CREATE_COMMENT
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -318,15 +334,11 @@ describe('compiler: v-if', () => {
|
|||||||
node: { codegenNode }
|
node: { codegenNode }
|
||||||
} = parseWithIfTransform(`<div v-if="ok"/>`)
|
} = parseWithIfTransform(`<div v-if="ok"/>`)
|
||||||
assertSharedCodegen(codegenNode)
|
assertSharedCodegen(codegenNode)
|
||||||
const branch1 = (codegenNode.expressions[1] as ConditionalExpression)
|
expect(codegenNode.consequent).toMatchObject({
|
||||||
.consequent as CallExpression
|
tag: `"div"`,
|
||||||
expect(branch1.arguments).toMatchObject([
|
props: createObjectMatcher({ key: `[0]` })
|
||||||
`"div"`,
|
})
|
||||||
createObjectMatcher({ key: `[0]` })
|
expect(codegenNode.alternate).toMatchObject({
|
||||||
])
|
|
||||||
const branch2 = (codegenNode.expressions[1] as ConditionalExpression)
|
|
||||||
.alternate as CallExpression
|
|
||||||
expect(branch2).toMatchObject({
|
|
||||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||||
callee: CREATE_COMMENT
|
callee: CREATE_COMMENT
|
||||||
})
|
})
|
||||||
@ -339,20 +351,16 @@ describe('compiler: v-if', () => {
|
|||||||
node: { codegenNode }
|
node: { codegenNode }
|
||||||
} = parseWithIfTransform(`<template v-if="ok"><div/>hello<p/></template>`)
|
} = parseWithIfTransform(`<template v-if="ok"><div/>hello<p/></template>`)
|
||||||
assertSharedCodegen(codegenNode)
|
assertSharedCodegen(codegenNode)
|
||||||
const branch1 = (codegenNode.expressions[1] as ConditionalExpression)
|
expect(codegenNode.consequent).toMatchObject({
|
||||||
.consequent as CallExpression
|
tag: FRAGMENT,
|
||||||
expect(branch1.arguments).toMatchObject([
|
props: createObjectMatcher({ key: `[0]` }),
|
||||||
FRAGMENT,
|
children: [
|
||||||
createObjectMatcher({ key: `[0]` }),
|
|
||||||
[
|
|
||||||
{ type: NodeTypes.ELEMENT, tag: 'div' },
|
{ type: NodeTypes.ELEMENT, tag: 'div' },
|
||||||
{ type: NodeTypes.TEXT, content: `hello` },
|
{ type: NodeTypes.TEXT, content: `hello` },
|
||||||
{ type: NodeTypes.ELEMENT, tag: 'p' }
|
{ type: NodeTypes.ELEMENT, tag: 'p' }
|
||||||
]
|
]
|
||||||
])
|
})
|
||||||
const branch2 = (codegenNode.expressions[1] as ConditionalExpression)
|
expect(codegenNode.alternate).toMatchObject({
|
||||||
.alternate as CallExpression
|
|
||||||
expect(branch2).toMatchObject({
|
|
||||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||||
callee: CREATE_COMMENT
|
callee: CREATE_COMMENT
|
||||||
})
|
})
|
||||||
@ -364,10 +372,7 @@ describe('compiler: v-if', () => {
|
|||||||
root,
|
root,
|
||||||
node: { codegenNode }
|
node: { codegenNode }
|
||||||
} = parseWithIfTransform(`<template v-if="ok"><slot/></template>`)
|
} = parseWithIfTransform(`<template v-if="ok"><slot/></template>`)
|
||||||
// assertSharedCodegen(codegenNode)
|
expect(codegenNode.consequent).toMatchObject({
|
||||||
const branch1 = (codegenNode.expressions[1] as ConditionalExpression)
|
|
||||||
.consequent as CallExpression
|
|
||||||
expect(branch1).toMatchObject({
|
|
||||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||||
callee: RENDER_SLOT,
|
callee: RENDER_SLOT,
|
||||||
arguments: ['$slots', '"default"', createObjectMatcher({ key: `[0]` })]
|
arguments: ['$slots', '"default"', createObjectMatcher({ key: `[0]` })]
|
||||||
@ -380,10 +385,7 @@ describe('compiler: v-if', () => {
|
|||||||
root,
|
root,
|
||||||
node: { codegenNode }
|
node: { codegenNode }
|
||||||
} = parseWithIfTransform(`<slot v-if="ok"></slot>`)
|
} = parseWithIfTransform(`<slot v-if="ok"></slot>`)
|
||||||
// assertSharedCodegen(codegenNode)
|
expect(codegenNode.consequent).toMatchObject({
|
||||||
const branch1 = (codegenNode.expressions[1] as ConditionalExpression)
|
|
||||||
.consequent as CallExpression
|
|
||||||
expect(branch1).toMatchObject({
|
|
||||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||||
callee: RENDER_SLOT,
|
callee: RENDER_SLOT,
|
||||||
arguments: ['$slots', '"default"', createObjectMatcher({ key: `[0]` })]
|
arguments: ['$slots', '"default"', createObjectMatcher({ key: `[0]` })]
|
||||||
@ -397,18 +399,14 @@ describe('compiler: v-if', () => {
|
|||||||
node: { codegenNode }
|
node: { codegenNode }
|
||||||
} = parseWithIfTransform(`<div v-if="ok"/><p v-else/>`)
|
} = parseWithIfTransform(`<div v-if="ok"/><p v-else/>`)
|
||||||
assertSharedCodegen(codegenNode, 0, true)
|
assertSharedCodegen(codegenNode, 0, true)
|
||||||
const branch1 = (codegenNode.expressions[1] as ConditionalExpression)
|
expect(codegenNode.consequent).toMatchObject({
|
||||||
.consequent as CallExpression
|
tag: `"div"`,
|
||||||
expect(branch1.arguments).toMatchObject([
|
props: createObjectMatcher({ key: `[0]` })
|
||||||
`"div"`,
|
})
|
||||||
createObjectMatcher({ key: `[0]` })
|
expect(codegenNode.alternate).toMatchObject({
|
||||||
])
|
tag: `"p"`,
|
||||||
const branch2 = (codegenNode.expressions[1] as ConditionalExpression)
|
props: createObjectMatcher({ key: `[1]` })
|
||||||
.alternate as CallExpression
|
})
|
||||||
expect(branch2.arguments).toMatchObject([
|
|
||||||
`"p"`,
|
|
||||||
createObjectMatcher({ key: `[1]` })
|
|
||||||
])
|
|
||||||
expect(generate(root).code).toMatchSnapshot()
|
expect(generate(root).code).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -418,18 +416,15 @@ describe('compiler: v-if', () => {
|
|||||||
node: { codegenNode }
|
node: { codegenNode }
|
||||||
} = parseWithIfTransform(`<div v-if="ok"/><p v-else-if="orNot" />`)
|
} = parseWithIfTransform(`<div v-if="ok"/><p v-else-if="orNot" />`)
|
||||||
assertSharedCodegen(codegenNode, 1)
|
assertSharedCodegen(codegenNode, 1)
|
||||||
const branch1 = (codegenNode.expressions[1] as ConditionalExpression)
|
expect(codegenNode.consequent).toMatchObject({
|
||||||
.consequent as CallExpression
|
tag: `"div"`,
|
||||||
expect(branch1.arguments).toMatchObject([
|
props: createObjectMatcher({ key: `[0]` })
|
||||||
`"div"`,
|
})
|
||||||
createObjectMatcher({ key: `[0]` })
|
const branch2 = codegenNode.alternate as ConditionalExpression
|
||||||
])
|
expect(branch2.consequent).toMatchObject({
|
||||||
const branch2 = (codegenNode.expressions[1] as ConditionalExpression)
|
tag: `"p"`,
|
||||||
.alternate as ConditionalExpression
|
props: createObjectMatcher({ key: `[1]` })
|
||||||
expect((branch2.consequent as CallExpression).arguments).toMatchObject([
|
})
|
||||||
`"p"`,
|
|
||||||
createObjectMatcher({ key: `[1]` })
|
|
||||||
])
|
|
||||||
expect(generate(root).code).toMatchSnapshot()
|
expect(generate(root).code).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -441,28 +436,25 @@ describe('compiler: v-if', () => {
|
|||||||
`<div v-if="ok"/><p v-else-if="orNot"/><template v-else>fine</template>`
|
`<div v-if="ok"/><p v-else-if="orNot"/><template v-else>fine</template>`
|
||||||
)
|
)
|
||||||
assertSharedCodegen(codegenNode, 1, true)
|
assertSharedCodegen(codegenNode, 1, true)
|
||||||
const branch1 = (codegenNode.expressions[1] as ConditionalExpression)
|
expect(codegenNode.consequent).toMatchObject({
|
||||||
.consequent as CallExpression
|
tag: `"div"`,
|
||||||
expect(branch1.arguments).toMatchObject([
|
props: createObjectMatcher({ key: `[0]` })
|
||||||
`"div"`,
|
})
|
||||||
createObjectMatcher({ key: `[0]` })
|
const branch2 = codegenNode.alternate as ConditionalExpression
|
||||||
])
|
expect(branch2.consequent).toMatchObject({
|
||||||
const branch2 = (codegenNode.expressions[1] as ConditionalExpression)
|
tag: `"p"`,
|
||||||
.alternate as ConditionalExpression
|
props: createObjectMatcher({ key: `[1]` })
|
||||||
expect((branch2.consequent as CallExpression).arguments).toMatchObject([
|
})
|
||||||
`"p"`,
|
expect(branch2.alternate).toMatchObject({
|
||||||
createObjectMatcher({ key: `[1]` })
|
tag: FRAGMENT,
|
||||||
])
|
props: createObjectMatcher({ key: `[2]` }),
|
||||||
expect((branch2.alternate as CallExpression).arguments).toMatchObject([
|
children: [
|
||||||
FRAGMENT,
|
|
||||||
createObjectMatcher({ key: `[2]` }),
|
|
||||||
[
|
|
||||||
{
|
{
|
||||||
type: NodeTypes.TEXT,
|
type: NodeTypes.TEXT,
|
||||||
content: `fine`
|
content: `fine`
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
])
|
})
|
||||||
expect(generate(root).code).toMatchSnapshot()
|
expect(generate(root).code).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -470,9 +462,8 @@ describe('compiler: v-if', () => {
|
|||||||
const {
|
const {
|
||||||
node: { codegenNode }
|
node: { codegenNode }
|
||||||
} = parseWithIfTransform(`<div v-if="ok" v-bind="obj"/>`)
|
} = parseWithIfTransform(`<div v-if="ok" v-bind="obj"/>`)
|
||||||
const branch1 = (codegenNode.expressions[1] as ConditionalExpression)
|
const branch1 = codegenNode.consequent as VNodeCall
|
||||||
.consequent as CallExpression
|
expect(branch1.props).toMatchObject({
|
||||||
expect(branch1.arguments[1]).toMatchObject({
|
|
||||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||||
callee: MERGE_PROPS,
|
callee: MERGE_PROPS,
|
||||||
arguments: [createObjectMatcher({ key: `[0]` }), { content: `obj` }]
|
arguments: [createObjectMatcher({ key: `[0]` }), { content: `obj` }]
|
||||||
@ -483,9 +474,8 @@ describe('compiler: v-if', () => {
|
|||||||
const {
|
const {
|
||||||
node: { codegenNode }
|
node: { codegenNode }
|
||||||
} = parseWithIfTransform(`<div v-if="ok" id="foo" v-bind="obj"/>`)
|
} = parseWithIfTransform(`<div v-if="ok" id="foo" v-bind="obj"/>`)
|
||||||
const branch1 = (codegenNode.expressions[1] as ConditionalExpression)
|
const branch1 = codegenNode.consequent as VNodeCall
|
||||||
.consequent as CallExpression
|
expect(branch1.props).toMatchObject({
|
||||||
expect(branch1.arguments[1]).toMatchObject({
|
|
||||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||||
callee: MERGE_PROPS,
|
callee: MERGE_PROPS,
|
||||||
arguments: [
|
arguments: [
|
||||||
@ -502,9 +492,8 @@ describe('compiler: v-if', () => {
|
|||||||
const {
|
const {
|
||||||
node: { codegenNode }
|
node: { codegenNode }
|
||||||
} = parseWithIfTransform(`<div v-if="ok" v-bind="obj" id="foo"/>`)
|
} = parseWithIfTransform(`<div v-if="ok" v-bind="obj" id="foo"/>`)
|
||||||
const branch1 = (codegenNode.expressions[1] as ConditionalExpression)
|
const branch1 = codegenNode.consequent as VNodeCall
|
||||||
.consequent as CallExpression
|
expect(branch1.props).toMatchObject({
|
||||||
expect(branch1.arguments[1]).toMatchObject({
|
|
||||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||||
callee: MERGE_PROPS,
|
callee: MERGE_PROPS,
|
||||||
arguments: [
|
arguments: [
|
||||||
@ -521,13 +510,21 @@ describe('compiler: v-if', () => {
|
|||||||
const {
|
const {
|
||||||
node: { codegenNode }
|
node: { codegenNode }
|
||||||
} = parseWithIfTransform(`<div v-if="ok" v-foo />`)
|
} = parseWithIfTransform(`<div v-if="ok" v-foo />`)
|
||||||
const branch1 = (codegenNode.expressions[1] as ConditionalExpression)
|
const branch1 = codegenNode.consequent as VNodeCall
|
||||||
.consequent as CallExpression
|
expect(branch1.directives).not.toBeUndefined()
|
||||||
expect(branch1.callee).toBe(WITH_DIRECTIVES)
|
expect(branch1.props).toMatchObject(createObjectMatcher({ key: `[0]` }))
|
||||||
const realBranch = branch1.arguments[0] as CallExpression
|
})
|
||||||
expect(realBranch.arguments[1]).toMatchObject(
|
|
||||||
createObjectMatcher({ key: `[0]` })
|
test('v-if with key', () => {
|
||||||
)
|
const {
|
||||||
|
root,
|
||||||
|
node: { codegenNode }
|
||||||
|
} = parseWithIfTransform(`<div v-if="ok" key="some-key"/>`)
|
||||||
|
expect(codegenNode.consequent).toMatchObject({
|
||||||
|
tag: `"div"`,
|
||||||
|
props: createObjectMatcher({ key: 'some-key' })
|
||||||
|
})
|
||||||
|
expect(generate(root).code).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
test.todo('with comments')
|
test.todo('with comments')
|
||||||
|
@ -1,16 +1,15 @@
|
|||||||
import {
|
import {
|
||||||
parse,
|
baseParse as parse,
|
||||||
transform,
|
transform,
|
||||||
generate,
|
generate,
|
||||||
ElementNode,
|
ElementNode,
|
||||||
ObjectExpression,
|
ObjectExpression,
|
||||||
CompilerOptions,
|
CompilerOptions,
|
||||||
CallExpression,
|
|
||||||
ForNode,
|
ForNode,
|
||||||
PlainElementNode,
|
PlainElementNode,
|
||||||
PlainElementCodegenNode,
|
|
||||||
ComponentNode,
|
ComponentNode,
|
||||||
NodeTypes
|
NodeTypes,
|
||||||
|
VNodeCall
|
||||||
} from '../../src'
|
} from '../../src'
|
||||||
import { ErrorCodes } from '../../src/errors'
|
import { ErrorCodes } from '../../src/errors'
|
||||||
import { transformModel } from '../../src/transforms/vModel'
|
import { transformModel } from '../../src/transforms/vModel'
|
||||||
@ -43,8 +42,8 @@ describe('compiler: transform v-model', () => {
|
|||||||
test('simple exprssion', () => {
|
test('simple exprssion', () => {
|
||||||
const root = parseWithVModel('<input v-model="model" />')
|
const root = parseWithVModel('<input v-model="model" />')
|
||||||
const node = root.children[0] as ElementNode
|
const node = root.children[0] as ElementNode
|
||||||
const props = ((node.codegenNode as CallExpression)
|
const props = ((node.codegenNode as VNodeCall).props as ObjectExpression)
|
||||||
.arguments[1] as ObjectExpression).properties
|
.properties
|
||||||
|
|
||||||
expect(props[0]).toMatchObject({
|
expect(props[0]).toMatchObject({
|
||||||
key: {
|
key: {
|
||||||
@ -82,8 +81,8 @@ describe('compiler: transform v-model', () => {
|
|||||||
prefixIdentifiers: true
|
prefixIdentifiers: true
|
||||||
})
|
})
|
||||||
const node = root.children[0] as ElementNode
|
const node = root.children[0] as ElementNode
|
||||||
const props = ((node.codegenNode as CallExpression)
|
const props = ((node.codegenNode as VNodeCall).props as ObjectExpression)
|
||||||
.arguments[1] as ObjectExpression).properties
|
.properties
|
||||||
|
|
||||||
expect(props[0]).toMatchObject({
|
expect(props[0]).toMatchObject({
|
||||||
key: {
|
key: {
|
||||||
@ -119,8 +118,8 @@ describe('compiler: transform v-model', () => {
|
|||||||
test('compound expression', () => {
|
test('compound expression', () => {
|
||||||
const root = parseWithVModel('<input v-model="model[index]" />')
|
const root = parseWithVModel('<input v-model="model[index]" />')
|
||||||
const node = root.children[0] as ElementNode
|
const node = root.children[0] as ElementNode
|
||||||
const props = ((node.codegenNode as CallExpression)
|
const props = ((node.codegenNode as VNodeCall).props as ObjectExpression)
|
||||||
.arguments[1] as ObjectExpression).properties
|
.properties
|
||||||
|
|
||||||
expect(props[0]).toMatchObject({
|
expect(props[0]).toMatchObject({
|
||||||
key: {
|
key: {
|
||||||
@ -158,8 +157,8 @@ describe('compiler: transform v-model', () => {
|
|||||||
prefixIdentifiers: true
|
prefixIdentifiers: true
|
||||||
})
|
})
|
||||||
const node = root.children[0] as ElementNode
|
const node = root.children[0] as ElementNode
|
||||||
const props = ((node.codegenNode as CallExpression)
|
const props = ((node.codegenNode as VNodeCall).props as ObjectExpression)
|
||||||
.arguments[1] as ObjectExpression).properties
|
.properties
|
||||||
|
|
||||||
expect(props[0]).toMatchObject({
|
expect(props[0]).toMatchObject({
|
||||||
key: {
|
key: {
|
||||||
@ -191,15 +190,19 @@ describe('compiler: transform v-model', () => {
|
|||||||
children: [
|
children: [
|
||||||
'$event => (',
|
'$event => (',
|
||||||
{
|
{
|
||||||
content: '_ctx.model',
|
children: [
|
||||||
isStatic: false
|
{
|
||||||
|
content: '_ctx.model',
|
||||||
|
isStatic: false
|
||||||
|
},
|
||||||
|
'[',
|
||||||
|
{
|
||||||
|
content: '_ctx.index',
|
||||||
|
isStatic: false
|
||||||
|
},
|
||||||
|
']'
|
||||||
|
]
|
||||||
},
|
},
|
||||||
'[',
|
|
||||||
{
|
|
||||||
content: '_ctx.index',
|
|
||||||
isStatic: false
|
|
||||||
},
|
|
||||||
']',
|
|
||||||
' = $event)'
|
' = $event)'
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -211,9 +214,8 @@ describe('compiler: transform v-model', () => {
|
|||||||
test('with argument', () => {
|
test('with argument', () => {
|
||||||
const root = parseWithVModel('<input v-model:value="model" />')
|
const root = parseWithVModel('<input v-model:value="model" />')
|
||||||
const node = root.children[0] as ElementNode
|
const node = root.children[0] as ElementNode
|
||||||
const props = ((node.codegenNode as CallExpression)
|
const props = ((node.codegenNode as VNodeCall).props as ObjectExpression)
|
||||||
.arguments[1] as ObjectExpression).properties
|
.properties
|
||||||
|
|
||||||
expect(props[0]).toMatchObject({
|
expect(props[0]).toMatchObject({
|
||||||
key: {
|
key: {
|
||||||
content: 'value',
|
content: 'value',
|
||||||
@ -248,8 +250,8 @@ describe('compiler: transform v-model', () => {
|
|||||||
test('with dynamic argument', () => {
|
test('with dynamic argument', () => {
|
||||||
const root = parseWithVModel('<input v-model:[value]="model" />')
|
const root = parseWithVModel('<input v-model:[value]="model" />')
|
||||||
const node = root.children[0] as ElementNode
|
const node = root.children[0] as ElementNode
|
||||||
const props = ((node.codegenNode as CallExpression)
|
const props = ((node.codegenNode as VNodeCall).props as ObjectExpression)
|
||||||
.arguments[1] as ObjectExpression).properties
|
.properties
|
||||||
|
|
||||||
expect(props[0]).toMatchObject({
|
expect(props[0]).toMatchObject({
|
||||||
key: {
|
key: {
|
||||||
@ -292,8 +294,8 @@ describe('compiler: transform v-model', () => {
|
|||||||
prefixIdentifiers: true
|
prefixIdentifiers: true
|
||||||
})
|
})
|
||||||
const node = root.children[0] as ElementNode
|
const node = root.children[0] as ElementNode
|
||||||
const props = ((node.codegenNode as CallExpression)
|
const props = ((node.codegenNode as VNodeCall).props as ObjectExpression)
|
||||||
.arguments[1] as ObjectExpression).properties
|
.properties
|
||||||
|
|
||||||
expect(props[0]).toMatchObject({
|
expect(props[0]).toMatchObject({
|
||||||
key: {
|
key: {
|
||||||
@ -338,12 +340,12 @@ describe('compiler: transform v-model', () => {
|
|||||||
})
|
})
|
||||||
expect(root.cached).toBe(1)
|
expect(root.cached).toBe(1)
|
||||||
const codegen = (root.children[0] as PlainElementNode)
|
const codegen = (root.children[0] as PlainElementNode)
|
||||||
.codegenNode as PlainElementCodegenNode
|
.codegenNode as VNodeCall
|
||||||
// should not list cached prop in dynamicProps
|
// should not list cached prop in dynamicProps
|
||||||
expect(codegen.arguments[4]).toBe(`["modelValue"]`)
|
expect(codegen.dynamicProps).toBe(`["modelValue"]`)
|
||||||
expect(
|
expect((codegen.props as ObjectExpression).properties[1].value.type).toBe(
|
||||||
(codegen.arguments[1] as ObjectExpression).properties[1].value.type
|
NodeTypes.JS_CACHE_EXPRESSION
|
||||||
).toBe(NodeTypes.JS_CACHE_EXPRESSION)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should not cache update handler if it refers v-for scope variables', () => {
|
test('should not cache update handler if it refers v-for scope variables', () => {
|
||||||
@ -356,10 +358,10 @@ describe('compiler: transform v-model', () => {
|
|||||||
)
|
)
|
||||||
expect(root.cached).toBe(0)
|
expect(root.cached).toBe(0)
|
||||||
const codegen = ((root.children[0] as ForNode)
|
const codegen = ((root.children[0] as ForNode)
|
||||||
.children[0] as PlainElementNode).codegenNode as PlainElementCodegenNode
|
.children[0] as PlainElementNode).codegenNode as VNodeCall
|
||||||
expect(codegen.arguments[4]).toBe(`["modelValue", "onUpdate:modelValue"]`)
|
expect(codegen.dynamicProps).toBe(`["modelValue", "onUpdate:modelValue"]`)
|
||||||
expect(
|
expect(
|
||||||
(codegen.arguments[1] as ObjectExpression).properties[1].value.type
|
(codegen.props as ObjectExpression).properties[1].value.type
|
||||||
).not.toBe(NodeTypes.JS_CACHE_EXPRESSION)
|
).not.toBe(NodeTypes.JS_CACHE_EXPRESSION)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -371,18 +373,18 @@ describe('compiler: transform v-model', () => {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
const codegen = ((root.children[0] as ComponentNode)
|
const codegen = ((root.children[0] as ComponentNode)
|
||||||
.children[0] as PlainElementNode).codegenNode as PlainElementCodegenNode
|
.children[0] as PlainElementNode).codegenNode as VNodeCall
|
||||||
expect(codegen.arguments[4]).toBe(`["modelValue", "onUpdate:modelValue"]`)
|
expect(codegen.dynamicProps).toBe(`["modelValue", "onUpdate:modelValue"]`)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should generate modelModifers for component v-model', () => {
|
test('should generate modelModifers for component v-model', () => {
|
||||||
const root = parseWithVModel('<Comp v-model.trim.bar-baz="foo" />', {
|
const root = parseWithVModel('<Comp v-model.trim.bar-baz="foo" />', {
|
||||||
prefixIdentifiers: true
|
prefixIdentifiers: true
|
||||||
})
|
})
|
||||||
const args = ((root.children[0] as ComponentNode)
|
const vnodeCall = (root.children[0] as ComponentNode)
|
||||||
.codegenNode as CallExpression).arguments
|
.codegenNode as VNodeCall
|
||||||
// props
|
// props
|
||||||
expect(args[1]).toMatchObject({
|
expect(vnodeCall.props).toMatchObject({
|
||||||
properties: [
|
properties: [
|
||||||
{ key: { content: `modelValue` } },
|
{ key: { content: `modelValue` } },
|
||||||
{ key: { content: `onUpdate:modelValue` } },
|
{ key: { content: `onUpdate:modelValue` } },
|
||||||
@ -394,7 +396,7 @@ describe('compiler: transform v-model', () => {
|
|||||||
})
|
})
|
||||||
// should NOT include modelModifiers in dynamicPropNames because it's never
|
// should NOT include modelModifiers in dynamicPropNames because it's never
|
||||||
// gonna change
|
// gonna change
|
||||||
expect(args[4]).toBe(`["modelValue", "onUpdate:modelValue"]`)
|
expect(vnodeCall.dynamicProps).toBe(`["modelValue", "onUpdate:modelValue"]`)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should generate modelModifers for component v-model with arguments', () => {
|
test('should generate modelModifers for component v-model with arguments', () => {
|
||||||
@ -404,10 +406,10 @@ describe('compiler: transform v-model', () => {
|
|||||||
prefixIdentifiers: true
|
prefixIdentifiers: true
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
const args = ((root.children[0] as ComponentNode)
|
const vnodeCall = (root.children[0] as ComponentNode)
|
||||||
.codegenNode as CallExpression).arguments
|
.codegenNode as VNodeCall
|
||||||
// props
|
// props
|
||||||
expect(args[1]).toMatchObject({
|
expect(vnodeCall.props).toMatchObject({
|
||||||
properties: [
|
properties: [
|
||||||
{ key: { content: `foo` } },
|
{ key: { content: `foo` } },
|
||||||
{ key: { content: `onUpdate:foo` } },
|
{ key: { content: `onUpdate:foo` } },
|
||||||
@ -425,7 +427,9 @@ describe('compiler: transform v-model', () => {
|
|||||||
})
|
})
|
||||||
// should NOT include modelModifiers in dynamicPropNames because it's never
|
// should NOT include modelModifiers in dynamicPropNames because it's never
|
||||||
// gonna change
|
// gonna change
|
||||||
expect(args[4]).toBe(`["foo", "onUpdate:foo", "bar", "onUpdate:bar"]`)
|
expect(vnodeCall.dynamicProps).toBe(
|
||||||
|
`["foo", "onUpdate:foo", "bar", "onUpdate:bar"]`
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('errors', () => {
|
describe('errors', () => {
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
import {
|
import {
|
||||||
parse,
|
baseParse as parse,
|
||||||
transform,
|
transform,
|
||||||
ElementNode,
|
ElementNode,
|
||||||
ObjectExpression,
|
ObjectExpression,
|
||||||
CompilerOptions,
|
CompilerOptions,
|
||||||
ErrorCodes,
|
ErrorCodes,
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
CallExpression,
|
VNodeCall
|
||||||
PlainElementCodegenNode
|
|
||||||
} from '../../src'
|
} from '../../src'
|
||||||
import { transformOn } from '../../src/transforms/vOn'
|
import { transformOn } from '../../src/transforms/vOn'
|
||||||
import { transformElement } from '../../src/transforms/transformElement'
|
import { transformElement } from '../../src/transforms/transformElement'
|
||||||
@ -31,54 +30,58 @@ function parseWithVOn(template: string, options: CompilerOptions = {}) {
|
|||||||
describe('compiler: transform v-on', () => {
|
describe('compiler: transform v-on', () => {
|
||||||
test('basic', () => {
|
test('basic', () => {
|
||||||
const { node } = parseWithVOn(`<div v-on:click="onClick"/>`)
|
const { node } = parseWithVOn(`<div v-on:click="onClick"/>`)
|
||||||
const props = (node.codegenNode as CallExpression)
|
expect((node.codegenNode as VNodeCall).props).toMatchObject({
|
||||||
.arguments[1] as ObjectExpression
|
properties: [
|
||||||
expect(props.properties[0]).toMatchObject({
|
{
|
||||||
key: {
|
key: {
|
||||||
content: `onClick`,
|
content: `onClick`,
|
||||||
isStatic: true,
|
isStatic: true,
|
||||||
loc: {
|
loc: {
|
||||||
start: {
|
start: {
|
||||||
line: 1,
|
line: 1,
|
||||||
column: 11
|
column: 11
|
||||||
|
},
|
||||||
|
end: {
|
||||||
|
line: 1,
|
||||||
|
column: 16
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
end: {
|
value: {
|
||||||
line: 1,
|
content: `onClick`,
|
||||||
column: 16
|
isStatic: false,
|
||||||
|
loc: {
|
||||||
|
start: {
|
||||||
|
line: 1,
|
||||||
|
column: 18
|
||||||
|
},
|
||||||
|
end: {
|
||||||
|
line: 1,
|
||||||
|
column: 25
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
]
|
||||||
value: {
|
|
||||||
content: `onClick`,
|
|
||||||
isStatic: false,
|
|
||||||
loc: {
|
|
||||||
start: {
|
|
||||||
line: 1,
|
|
||||||
column: 18
|
|
||||||
},
|
|
||||||
end: {
|
|
||||||
line: 1,
|
|
||||||
column: 25
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test('dynamic arg', () => {
|
test('dynamic arg', () => {
|
||||||
const { node } = parseWithVOn(`<div v-on:[event]="handler"/>`)
|
const { node } = parseWithVOn(`<div v-on:[event]="handler"/>`)
|
||||||
const props = (node.codegenNode as CallExpression)
|
expect((node.codegenNode as VNodeCall).props).toMatchObject({
|
||||||
.arguments[1] as ObjectExpression
|
properties: [
|
||||||
expect(props.properties[0]).toMatchObject({
|
{
|
||||||
key: {
|
key: {
|
||||||
type: NodeTypes.COMPOUND_EXPRESSION,
|
type: NodeTypes.COMPOUND_EXPRESSION,
|
||||||
children: [`"on" + (`, { content: `event` }, `)`]
|
children: [`"on" + (`, { content: `event` }, `)`]
|
||||||
},
|
},
|
||||||
value: {
|
value: {
|
||||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
content: `handler`,
|
content: `handler`,
|
||||||
isStatic: false
|
isStatic: false
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -86,18 +89,20 @@ describe('compiler: transform v-on', () => {
|
|||||||
const { node } = parseWithVOn(`<div v-on:[event]="handler"/>`, {
|
const { node } = parseWithVOn(`<div v-on:[event]="handler"/>`, {
|
||||||
prefixIdentifiers: true
|
prefixIdentifiers: true
|
||||||
})
|
})
|
||||||
const props = (node.codegenNode as CallExpression)
|
expect((node.codegenNode as VNodeCall).props).toMatchObject({
|
||||||
.arguments[1] as ObjectExpression
|
properties: [
|
||||||
expect(props.properties[0]).toMatchObject({
|
{
|
||||||
key: {
|
key: {
|
||||||
type: NodeTypes.COMPOUND_EXPRESSION,
|
type: NodeTypes.COMPOUND_EXPRESSION,
|
||||||
children: [`"on" + (`, { content: `_ctx.event` }, `)`]
|
children: [`"on" + (`, { content: `_ctx.event` }, `)`]
|
||||||
},
|
},
|
||||||
value: {
|
value: {
|
||||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
content: `_ctx.handler`,
|
content: `_ctx.handler`,
|
||||||
isStatic: false
|
isStatic: false
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -105,38 +110,60 @@ describe('compiler: transform v-on', () => {
|
|||||||
const { node } = parseWithVOn(`<div v-on:[event(foo)]="handler"/>`, {
|
const { node } = parseWithVOn(`<div v-on:[event(foo)]="handler"/>`, {
|
||||||
prefixIdentifiers: true
|
prefixIdentifiers: true
|
||||||
})
|
})
|
||||||
const props = (node.codegenNode as CallExpression)
|
expect((node.codegenNode as VNodeCall).props).toMatchObject({
|
||||||
.arguments[1] as ObjectExpression
|
properties: [
|
||||||
expect(props.properties[0]).toMatchObject({
|
{
|
||||||
key: {
|
key: {
|
||||||
type: NodeTypes.COMPOUND_EXPRESSION,
|
type: NodeTypes.COMPOUND_EXPRESSION,
|
||||||
children: [
|
children: [
|
||||||
`"on" + (`,
|
`"on" + (`,
|
||||||
{ content: `_ctx.event` },
|
{ content: `_ctx.event` },
|
||||||
`(`,
|
`(`,
|
||||||
{ content: `_ctx.foo` },
|
{ content: `_ctx.foo` },
|
||||||
`)`,
|
`)`,
|
||||||
`)`
|
`)`
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
value: {
|
value: {
|
||||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
content: `_ctx.handler`,
|
content: `_ctx.handler`,
|
||||||
isStatic: false
|
isStatic: false
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should wrap as function if expression is inline statement', () => {
|
test('should wrap as function if expression is inline statement', () => {
|
||||||
const { node } = parseWithVOn(`<div @click="i++"/>`)
|
const { node } = parseWithVOn(`<div @click="i++"/>`)
|
||||||
const props = (node.codegenNode as CallExpression)
|
expect((node.codegenNode as VNodeCall).props).toMatchObject({
|
||||||
.arguments[1] as ObjectExpression
|
properties: [
|
||||||
expect(props.properties[0]).toMatchObject({
|
{
|
||||||
key: { content: `onClick` },
|
key: { content: `onClick` },
|
||||||
value: {
|
value: {
|
||||||
type: NodeTypes.COMPOUND_EXPRESSION,
|
type: NodeTypes.COMPOUND_EXPRESSION,
|
||||||
children: [`$event => (`, { content: `i++` }, `)`]
|
children: [`$event => (`, { content: `i++` }, `)`]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should handle multiple inline statement', () => {
|
||||||
|
const { node } = parseWithVOn(`<div @click="foo();bar()"/>`)
|
||||||
|
expect((node.codegenNode as VNodeCall).props).toMatchObject({
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
key: { content: `onClick` },
|
||||||
|
value: {
|
||||||
|
type: NodeTypes.COMPOUND_EXPRESSION,
|
||||||
|
// should wrap with `{` for multiple statements
|
||||||
|
// in this case the return value is discarded and the behavior is
|
||||||
|
// consistent with 2.x
|
||||||
|
children: [`$event => {`, { content: `foo();bar()` }, `}`]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -144,48 +171,90 @@ describe('compiler: transform v-on', () => {
|
|||||||
const { node } = parseWithVOn(`<div @click="foo($event)"/>`, {
|
const { node } = parseWithVOn(`<div @click="foo($event)"/>`, {
|
||||||
prefixIdentifiers: true
|
prefixIdentifiers: true
|
||||||
})
|
})
|
||||||
const props = (node.codegenNode as CallExpression)
|
expect((node.codegenNode as VNodeCall).props).toMatchObject({
|
||||||
.arguments[1] as ObjectExpression
|
properties: [
|
||||||
expect(props.properties[0]).toMatchObject({
|
{
|
||||||
key: { content: `onClick` },
|
key: { content: `onClick` },
|
||||||
value: {
|
value: {
|
||||||
type: NodeTypes.COMPOUND_EXPRESSION,
|
type: NodeTypes.COMPOUND_EXPRESSION,
|
||||||
children: [
|
children: [
|
||||||
`$event => (`,
|
`$event => (`,
|
||||||
{ content: `_ctx.foo` },
|
{
|
||||||
`(`,
|
type: NodeTypes.COMPOUND_EXPRESSION,
|
||||||
// should NOT prefix $event
|
children: [
|
||||||
{ content: `$event` },
|
{ content: `_ctx.foo` },
|
||||||
`)`,
|
`(`,
|
||||||
`)`
|
// should NOT prefix $event
|
||||||
]
|
{ content: `$event` },
|
||||||
}
|
`)`
|
||||||
|
]
|
||||||
|
},
|
||||||
|
`)`
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('multiple inline statements w/ prefixIdentifiers: true', () => {
|
||||||
|
const { node } = parseWithVOn(`<div @click="foo($event);bar()"/>`, {
|
||||||
|
prefixIdentifiers: true
|
||||||
|
})
|
||||||
|
expect((node.codegenNode as VNodeCall).props).toMatchObject({
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
key: { content: `onClick` },
|
||||||
|
value: {
|
||||||
|
type: NodeTypes.COMPOUND_EXPRESSION,
|
||||||
|
children: [
|
||||||
|
`$event => {`,
|
||||||
|
{
|
||||||
|
children: [
|
||||||
|
{ content: `_ctx.foo` },
|
||||||
|
`(`,
|
||||||
|
// should NOT prefix $event
|
||||||
|
{ content: `$event` },
|
||||||
|
`);`,
|
||||||
|
{ content: `_ctx.bar` },
|
||||||
|
`()`
|
||||||
|
]
|
||||||
|
},
|
||||||
|
`}`
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should NOT wrap as function if expression is already function expression', () => {
|
test('should NOT wrap as function if expression is already function expression', () => {
|
||||||
const { node } = parseWithVOn(`<div @click="$event => foo($event)"/>`)
|
const { node } = parseWithVOn(`<div @click="$event => foo($event)"/>`)
|
||||||
const props = (node.codegenNode as CallExpression)
|
expect((node.codegenNode as VNodeCall).props).toMatchObject({
|
||||||
.arguments[1] as ObjectExpression
|
properties: [
|
||||||
expect(props.properties[0]).toMatchObject({
|
{
|
||||||
key: { content: `onClick` },
|
key: { content: `onClick` },
|
||||||
value: {
|
value: {
|
||||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
content: `$event => foo($event)`
|
content: `$event => foo($event)`
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should NOT wrap as function if expression is complex member expression', () => {
|
test('should NOT wrap as function if expression is complex member expression', () => {
|
||||||
const { node } = parseWithVOn(`<div @click="a['b' + c]"/>`)
|
const { node } = parseWithVOn(`<div @click="a['b' + c]"/>`)
|
||||||
const props = (node.codegenNode as CallExpression)
|
expect((node.codegenNode as VNodeCall).props).toMatchObject({
|
||||||
.arguments[1] as ObjectExpression
|
properties: [
|
||||||
expect(props.properties[0]).toMatchObject({
|
{
|
||||||
key: { content: `onClick` },
|
key: { content: `onClick` },
|
||||||
value: {
|
value: {
|
||||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
content: `a['b' + c]`
|
content: `a['b' + c]`
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -193,14 +262,21 @@ describe('compiler: transform v-on', () => {
|
|||||||
const { node } = parseWithVOn(`<div @click="a['b' + c]"/>`, {
|
const { node } = parseWithVOn(`<div @click="a['b' + c]"/>`, {
|
||||||
prefixIdentifiers: true
|
prefixIdentifiers: true
|
||||||
})
|
})
|
||||||
const props = (node.codegenNode as CallExpression)
|
expect((node.codegenNode as VNodeCall).props).toMatchObject({
|
||||||
.arguments[1] as ObjectExpression
|
properties: [
|
||||||
expect(props.properties[0]).toMatchObject({
|
{
|
||||||
key: { content: `onClick` },
|
key: { content: `onClick` },
|
||||||
value: {
|
value: {
|
||||||
type: NodeTypes.COMPOUND_EXPRESSION,
|
type: NodeTypes.COMPOUND_EXPRESSION,
|
||||||
children: [{ content: `_ctx.a` }, `['b' + `, { content: `_ctx.c` }, `]`]
|
children: [
|
||||||
}
|
{ content: `_ctx.a` },
|
||||||
|
`['b' + `,
|
||||||
|
{ content: `_ctx.c` },
|
||||||
|
`]`
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -208,21 +284,23 @@ describe('compiler: transform v-on', () => {
|
|||||||
const { node } = parseWithVOn(`<div @click="e => foo(e)"/>`, {
|
const { node } = parseWithVOn(`<div @click="e => foo(e)"/>`, {
|
||||||
prefixIdentifiers: true
|
prefixIdentifiers: true
|
||||||
})
|
})
|
||||||
const props = (node.codegenNode as CallExpression)
|
expect((node.codegenNode as VNodeCall).props).toMatchObject({
|
||||||
.arguments[1] as ObjectExpression
|
properties: [
|
||||||
expect(props.properties[0]).toMatchObject({
|
{
|
||||||
key: { content: `onClick` },
|
key: { content: `onClick` },
|
||||||
value: {
|
value: {
|
||||||
type: NodeTypes.COMPOUND_EXPRESSION,
|
type: NodeTypes.COMPOUND_EXPRESSION,
|
||||||
children: [
|
children: [
|
||||||
{ content: `e` },
|
{ content: `e` },
|
||||||
` => `,
|
` => `,
|
||||||
{ content: `_ctx.foo` },
|
{ content: `_ctx.foo` },
|
||||||
`(`,
|
`(`,
|
||||||
{ content: `e` },
|
{ content: `e` },
|
||||||
`)`
|
`)`
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -250,6 +328,22 @@ describe('compiler: transform v-on', () => {
|
|||||||
expect(onError).not.toHaveBeenCalled()
|
expect(onError).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('case conversion for vnode hooks', () => {
|
||||||
|
const { node } = parseWithVOn(`<div v-on:vnode-mounted="onMount"/>`)
|
||||||
|
expect((node.codegenNode as VNodeCall).props).toMatchObject({
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
key: {
|
||||||
|
content: `onVnodeMounted`
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
content: `onMount`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('cacheHandler', () => {
|
describe('cacheHandler', () => {
|
||||||
test('empty handler', () => {
|
test('empty handler', () => {
|
||||||
const { root, node } = parseWithVOn(`<div v-on:click.prevent />`, {
|
const { root, node } = parseWithVOn(`<div v-on:click.prevent />`, {
|
||||||
@ -257,10 +351,12 @@ describe('compiler: transform v-on', () => {
|
|||||||
cacheHandlers: true
|
cacheHandlers: true
|
||||||
})
|
})
|
||||||
expect(root.cached).toBe(1)
|
expect(root.cached).toBe(1)
|
||||||
const args = (node.codegenNode as PlainElementCodegenNode).arguments
|
const vnodeCall = node.codegenNode as VNodeCall
|
||||||
// should not treat cached handler as dynamicProp, so no flags
|
// should not treat cached handler as dynamicProp, so no flags
|
||||||
expect(args.length).toBe(2)
|
expect(vnodeCall.patchFlag).toBeUndefined()
|
||||||
expect((args[1] as ObjectExpression).properties[0].value).toMatchObject({
|
expect(
|
||||||
|
(vnodeCall.props as ObjectExpression).properties[0].value
|
||||||
|
).toMatchObject({
|
||||||
type: NodeTypes.JS_CACHE_EXPRESSION,
|
type: NodeTypes.JS_CACHE_EXPRESSION,
|
||||||
index: 1,
|
index: 1,
|
||||||
value: {
|
value: {
|
||||||
@ -276,10 +372,12 @@ describe('compiler: transform v-on', () => {
|
|||||||
cacheHandlers: true
|
cacheHandlers: true
|
||||||
})
|
})
|
||||||
expect(root.cached).toBe(1)
|
expect(root.cached).toBe(1)
|
||||||
const args = (node.codegenNode as PlainElementCodegenNode).arguments
|
const vnodeCall = node.codegenNode as VNodeCall
|
||||||
// should not treat cached handler as dynamicProp, so no flags
|
// should not treat cached handler as dynamicProp, so no flags
|
||||||
expect(args.length).toBe(2)
|
expect(vnodeCall.patchFlag).toBeUndefined()
|
||||||
expect((args[1] as ObjectExpression).properties[0].value).toMatchObject({
|
expect(
|
||||||
|
(vnodeCall.props as ObjectExpression).properties[0].value
|
||||||
|
).toMatchObject({
|
||||||
type: NodeTypes.JS_CACHE_EXPRESSION,
|
type: NodeTypes.JS_CACHE_EXPRESSION,
|
||||||
index: 1,
|
index: 1,
|
||||||
value: {
|
value: {
|
||||||
@ -295,10 +393,12 @@ describe('compiler: transform v-on', () => {
|
|||||||
cacheHandlers: true
|
cacheHandlers: true
|
||||||
})
|
})
|
||||||
expect(root.cached).toBe(1)
|
expect(root.cached).toBe(1)
|
||||||
const args = (node.codegenNode as PlainElementCodegenNode).arguments
|
const vnodeCall = node.codegenNode as VNodeCall
|
||||||
// should not treat cached handler as dynamicProp, so no flags
|
// should not treat cached handler as dynamicProp, so no flags
|
||||||
expect(args.length).toBe(2)
|
expect(vnodeCall.patchFlag).toBeUndefined()
|
||||||
expect((args[1] as ObjectExpression).properties[0].value).toMatchObject({
|
expect(
|
||||||
|
(vnodeCall.props as ObjectExpression).properties[0].value
|
||||||
|
).toMatchObject({
|
||||||
type: NodeTypes.JS_CACHE_EXPRESSION,
|
type: NodeTypes.JS_CACHE_EXPRESSION,
|
||||||
index: 1,
|
index: 1,
|
||||||
value: {
|
value: {
|
||||||
@ -314,15 +414,22 @@ describe('compiler: transform v-on', () => {
|
|||||||
cacheHandlers: true
|
cacheHandlers: true
|
||||||
})
|
})
|
||||||
expect(root.cached).toBe(1)
|
expect(root.cached).toBe(1)
|
||||||
const args = (node.codegenNode as PlainElementCodegenNode).arguments
|
expect(root.cached).toBe(1)
|
||||||
|
const vnodeCall = node.codegenNode as VNodeCall
|
||||||
// should not treat cached handler as dynamicProp, so no flags
|
// should not treat cached handler as dynamicProp, so no flags
|
||||||
expect(args.length).toBe(2)
|
expect(vnodeCall.patchFlag).toBeUndefined()
|
||||||
expect((args[1] as ObjectExpression).properties[0].value).toMatchObject({
|
expect(
|
||||||
|
(vnodeCall.props as ObjectExpression).properties[0].value
|
||||||
|
).toMatchObject({
|
||||||
type: NodeTypes.JS_CACHE_EXPRESSION,
|
type: NodeTypes.JS_CACHE_EXPRESSION,
|
||||||
index: 1,
|
index: 1,
|
||||||
value: {
|
value: {
|
||||||
type: NodeTypes.COMPOUND_EXPRESSION,
|
type: NodeTypes.COMPOUND_EXPRESSION,
|
||||||
children: [`$event => (`, { content: `_ctx.foo` }, `++`, `)`]
|
children: [
|
||||||
|
`$event => (`,
|
||||||
|
{ children: [{ content: `_ctx.foo` }, `++`] },
|
||||||
|
`)`
|
||||||
|
]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
parse,
|
baseParse as parse,
|
||||||
transform,
|
transform,
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
generate,
|
generate,
|
||||||
@ -7,11 +7,7 @@ import {
|
|||||||
} from '../../src'
|
} from '../../src'
|
||||||
import { transformOnce } from '../../src/transforms/vOnce'
|
import { transformOnce } from '../../src/transforms/vOnce'
|
||||||
import { transformElement } from '../../src/transforms/transformElement'
|
import { transformElement } from '../../src/transforms/transformElement'
|
||||||
import {
|
import { RENDER_SLOT, SET_BLOCK_TRACKING } from '../../src/runtimeHelpers'
|
||||||
CREATE_VNODE,
|
|
||||||
RENDER_SLOT,
|
|
||||||
SET_BLOCK_TRACKING
|
|
||||||
} from '../../src/runtimeHelpers'
|
|
||||||
import { transformBind } from '../../src/transforms/vBind'
|
import { transformBind } from '../../src/transforms/vBind'
|
||||||
import { transformSlotOutlet } from '../../src/transforms/transformSlotOutlet'
|
import { transformSlotOutlet } from '../../src/transforms/transformSlotOutlet'
|
||||||
|
|
||||||
@ -36,8 +32,8 @@ describe('compiler: v-once transform', () => {
|
|||||||
type: NodeTypes.JS_CACHE_EXPRESSION,
|
type: NodeTypes.JS_CACHE_EXPRESSION,
|
||||||
index: 1,
|
index: 1,
|
||||||
value: {
|
value: {
|
||||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
type: NodeTypes.VNODE_CALL,
|
||||||
callee: CREATE_VNODE
|
tag: `"div"`
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
expect(generate(root).code).toMatchSnapshot()
|
expect(generate(root).code).toMatchSnapshot()
|
||||||
@ -51,8 +47,8 @@ describe('compiler: v-once transform', () => {
|
|||||||
type: NodeTypes.JS_CACHE_EXPRESSION,
|
type: NodeTypes.JS_CACHE_EXPRESSION,
|
||||||
index: 1,
|
index: 1,
|
||||||
value: {
|
value: {
|
||||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
type: NodeTypes.VNODE_CALL,
|
||||||
callee: CREATE_VNODE
|
tag: `"div"`
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
expect(generate(root).code).toMatchSnapshot()
|
expect(generate(root).code).toMatchSnapshot()
|
||||||
@ -66,8 +62,8 @@ describe('compiler: v-once transform', () => {
|
|||||||
type: NodeTypes.JS_CACHE_EXPRESSION,
|
type: NodeTypes.JS_CACHE_EXPRESSION,
|
||||||
index: 1,
|
index: 1,
|
||||||
value: {
|
value: {
|
||||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
type: NodeTypes.VNODE_CALL,
|
||||||
callee: CREATE_VNODE
|
tag: `_component_Comp`
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
expect(generate(root).code).toMatchSnapshot()
|
expect(generate(root).code).toMatchSnapshot()
|
||||||
@ -100,8 +96,8 @@ describe('compiler: v-once transform', () => {
|
|||||||
type: NodeTypes.JS_CACHE_EXPRESSION,
|
type: NodeTypes.JS_CACHE_EXPRESSION,
|
||||||
index: 1,
|
index: 1,
|
||||||
value: {
|
value: {
|
||||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
type: NodeTypes.VNODE_CALL,
|
||||||
callee: CREATE_VNODE
|
tag: `"div"`
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
expect(generate(root).code).toMatchSnapshot()
|
expect(generate(root).code).toMatchSnapshot()
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
import {
|
import {
|
||||||
CompilerOptions,
|
CompilerOptions,
|
||||||
parse,
|
baseParse as parse,
|
||||||
transform,
|
transform,
|
||||||
generate,
|
generate,
|
||||||
ElementNode,
|
ElementNode,
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
ErrorCodes,
|
ErrorCodes,
|
||||||
ForNode,
|
ForNode,
|
||||||
CallExpression,
|
ComponentNode,
|
||||||
ComponentNode
|
VNodeCall,
|
||||||
|
SlotsExpression
|
||||||
} from '../../src'
|
} from '../../src'
|
||||||
import { transformElement } from '../../src/transforms/transformElement'
|
import { transformElement } from '../../src/transforms/transformElement'
|
||||||
import { transformOn } from '../../src/transforms/vOn'
|
import { transformOn } from '../../src/transforms/vOn'
|
||||||
@ -46,7 +47,8 @@ function parseWithSlots(template: string, options: CompilerOptions = {}) {
|
|||||||
root: ast,
|
root: ast,
|
||||||
slots:
|
slots:
|
||||||
ast.children[0].type === NodeTypes.ELEMENT
|
ast.children[0].type === NodeTypes.ELEMENT
|
||||||
? (ast.children[0].codegenNode as CallExpression).arguments[2]
|
? ((ast.children[0].codegenNode as VNodeCall)
|
||||||
|
.children as SlotsExpression)
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -67,8 +69,8 @@ function createSlotMatcher(obj: Record<string, any>) {
|
|||||||
} as any
|
} as any
|
||||||
})
|
})
|
||||||
.concat({
|
.concat({
|
||||||
key: { content: `_compiled` },
|
key: { content: `_` },
|
||||||
value: { content: `true` }
|
value: { content: `1`, isStatic: false }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -95,7 +97,7 @@ describe('compiler: transform component slots', () => {
|
|||||||
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
|
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('explicit default slot', () => {
|
test('on-component default slot', () => {
|
||||||
const { root, slots } = parseWithSlots(
|
const { root, slots } = parseWithSlots(
|
||||||
`<Comp v-slot="{ foo }">{{ foo }}{{ bar }}</Comp>`,
|
`<Comp v-slot="{ foo }">{{ foo }}{{ bar }}</Comp>`,
|
||||||
{ prefixIdentifiers: true }
|
{ prefixIdentifiers: true }
|
||||||
@ -128,7 +130,40 @@ describe('compiler: transform component slots', () => {
|
|||||||
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
|
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('named slots', () => {
|
test('on component named slot', () => {
|
||||||
|
const { root, slots } = parseWithSlots(
|
||||||
|
`<Comp v-slot:named="{ foo }">{{ foo }}{{ bar }}</Comp>`,
|
||||||
|
{ prefixIdentifiers: true }
|
||||||
|
)
|
||||||
|
expect(slots).toMatchObject(
|
||||||
|
createSlotMatcher({
|
||||||
|
named: {
|
||||||
|
type: NodeTypes.JS_FUNCTION_EXPRESSION,
|
||||||
|
params: {
|
||||||
|
type: NodeTypes.COMPOUND_EXPRESSION,
|
||||||
|
children: [`{ `, { content: `foo` }, ` }`]
|
||||||
|
},
|
||||||
|
returns: [
|
||||||
|
{
|
||||||
|
type: NodeTypes.INTERPOLATION,
|
||||||
|
content: {
|
||||||
|
content: `foo`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: NodeTypes.INTERPOLATION,
|
||||||
|
content: {
|
||||||
|
content: `_ctx.bar`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('template named slots', () => {
|
||||||
const { root, slots } = parseWithSlots(
|
const { root, slots } = parseWithSlots(
|
||||||
`<Comp>
|
`<Comp>
|
||||||
<template v-slot:one="{ foo }">
|
<template v-slot:one="{ foo }">
|
||||||
@ -189,6 +224,76 @@ describe('compiler: transform component slots', () => {
|
|||||||
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
|
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('on component dynamically named slot', () => {
|
||||||
|
const { root, slots } = parseWithSlots(
|
||||||
|
`<Comp v-slot:[named]="{ foo }">{{ foo }}{{ bar }}</Comp>`,
|
||||||
|
{ prefixIdentifiers: true }
|
||||||
|
)
|
||||||
|
expect(slots).toMatchObject(
|
||||||
|
createSlotMatcher({
|
||||||
|
'[_ctx.named]': {
|
||||||
|
type: NodeTypes.JS_FUNCTION_EXPRESSION,
|
||||||
|
params: {
|
||||||
|
type: NodeTypes.COMPOUND_EXPRESSION,
|
||||||
|
children: [`{ `, { content: `foo` }, ` }`]
|
||||||
|
},
|
||||||
|
returns: [
|
||||||
|
{
|
||||||
|
type: NodeTypes.INTERPOLATION,
|
||||||
|
content: {
|
||||||
|
content: `foo`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: NodeTypes.INTERPOLATION,
|
||||||
|
content: {
|
||||||
|
content: `_ctx.bar`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('named slots w/ implicit default slot', () => {
|
||||||
|
const { root, slots } = parseWithSlots(
|
||||||
|
`<Comp>
|
||||||
|
<template #one>foo</template>bar<span/>
|
||||||
|
</Comp>`
|
||||||
|
)
|
||||||
|
expect(slots).toMatchObject(
|
||||||
|
createSlotMatcher({
|
||||||
|
one: {
|
||||||
|
type: NodeTypes.JS_FUNCTION_EXPRESSION,
|
||||||
|
params: undefined,
|
||||||
|
returns: [
|
||||||
|
{
|
||||||
|
type: NodeTypes.TEXT,
|
||||||
|
content: `foo`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
default: {
|
||||||
|
type: NodeTypes.JS_FUNCTION_EXPRESSION,
|
||||||
|
params: undefined,
|
||||||
|
returns: [
|
||||||
|
{
|
||||||
|
type: NodeTypes.TEXT,
|
||||||
|
content: `bar`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: NodeTypes.ELEMENT,
|
||||||
|
tag: `span`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
expect(generate(root).code).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
test('dynamically named slots', () => {
|
test('dynamically named slots', () => {
|
||||||
const { root, slots } = parseWithSlots(
|
const { root, slots } = parseWithSlots(
|
||||||
`<Comp>
|
`<Comp>
|
||||||
@ -274,43 +379,41 @@ describe('compiler: transform component slots', () => {
|
|||||||
{
|
{
|
||||||
type: NodeTypes.ELEMENT,
|
type: NodeTypes.ELEMENT,
|
||||||
codegenNode: {
|
codegenNode: {
|
||||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
type: NodeTypes.VNODE_CALL,
|
||||||
arguments: [
|
tag: `_component_Inner`,
|
||||||
`_component_Inner`,
|
props: undefined,
|
||||||
`null`,
|
children: createSlotMatcher({
|
||||||
createSlotMatcher({
|
default: {
|
||||||
default: {
|
type: NodeTypes.JS_FUNCTION_EXPRESSION,
|
||||||
type: NodeTypes.JS_FUNCTION_EXPRESSION,
|
params: {
|
||||||
params: {
|
type: NodeTypes.COMPOUND_EXPRESSION,
|
||||||
type: NodeTypes.COMPOUND_EXPRESSION,
|
children: [`{ `, { content: `bar` }, ` }`]
|
||||||
children: [`{ `, { content: `bar` }, ` }`]
|
},
|
||||||
},
|
returns: [
|
||||||
returns: [
|
{
|
||||||
{
|
type: NodeTypes.INTERPOLATION,
|
||||||
type: NodeTypes.INTERPOLATION,
|
content: {
|
||||||
content: {
|
content: `foo`
|
||||||
content: `foo`
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: NodeTypes.INTERPOLATION,
|
|
||||||
content: {
|
|
||||||
content: `bar`
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: NodeTypes.INTERPOLATION,
|
|
||||||
content: {
|
|
||||||
content: `_ctx.baz`
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
},
|
||||||
}
|
{
|
||||||
}),
|
type: NodeTypes.INTERPOLATION,
|
||||||
// nested slot should be forced dynamic, since scope variables
|
content: {
|
||||||
// are not tracked as dependencies of the slot.
|
content: `bar`
|
||||||
genFlagText(PatchFlags.DYNAMIC_SLOTS)
|
}
|
||||||
]
|
},
|
||||||
|
{
|
||||||
|
type: NodeTypes.INTERPOLATION,
|
||||||
|
content: {
|
||||||
|
content: `_ctx.baz`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
// nested slot should be forced dynamic, since scope variables
|
||||||
|
// are not tracked as dependencies of the slot.
|
||||||
|
patchFlag: genFlagText(PatchFlags.DYNAMIC_SLOTS)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// test scope
|
// test scope
|
||||||
@ -351,8 +454,8 @@ describe('compiler: transform component slots', () => {
|
|||||||
)
|
)
|
||||||
const div = ((root.children[0] as ForNode).children[0] as ElementNode)
|
const div = ((root.children[0] as ForNode).children[0] as ElementNode)
|
||||||
.codegenNode as any
|
.codegenNode as any
|
||||||
const comp = div.arguments[2][0]
|
const comp = div.children[0]
|
||||||
expect(comp.codegenNode.arguments[3]).toBe(
|
expect(comp.codegenNode.patchFlag).toBe(
|
||||||
genFlagText(PatchFlags.DYNAMIC_SLOTS)
|
genFlagText(PatchFlags.DYNAMIC_SLOTS)
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@ -364,12 +467,12 @@ describe('compiler: transform component slots', () => {
|
|||||||
if (root.children[0].type === NodeTypes.FOR) {
|
if (root.children[0].type === NodeTypes.FOR) {
|
||||||
const div = (root.children[0].children[0] as ElementNode)
|
const div = (root.children[0].children[0] as ElementNode)
|
||||||
.codegenNode as any
|
.codegenNode as any
|
||||||
const comp = div.arguments[2][0]
|
const comp = div.children[0]
|
||||||
flag = comp.codegenNode.arguments[3]
|
flag = comp.codegenNode.patchFlag
|
||||||
} else {
|
} else {
|
||||||
const innerComp = (root.children[0] as ComponentNode)
|
const innerComp = (root.children[0] as ComponentNode)
|
||||||
.children[0] as ComponentNode
|
.children[0] as ComponentNode
|
||||||
flag = (innerComp.codegenNode as CallExpression).arguments[3]
|
flag = (innerComp.codegenNode as VNodeCall).patchFlag
|
||||||
}
|
}
|
||||||
if (shouldForce) {
|
if (shouldForce) {
|
||||||
expect(flag).toBe(genFlagText(PatchFlags.DYNAMIC_SLOTS))
|
expect(flag).toBe(genFlagText(PatchFlags.DYNAMIC_SLOTS))
|
||||||
@ -419,7 +522,7 @@ describe('compiler: transform component slots', () => {
|
|||||||
callee: CREATE_SLOTS,
|
callee: CREATE_SLOTS,
|
||||||
arguments: [
|
arguments: [
|
||||||
createObjectMatcher({
|
createObjectMatcher({
|
||||||
_compiled: `[true]`
|
_: `[1]`
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
type: NodeTypes.JS_ARRAY_EXPRESSION,
|
type: NodeTypes.JS_ARRAY_EXPRESSION,
|
||||||
@ -443,7 +546,7 @@ describe('compiler: transform component slots', () => {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
expect((root as any).children[0].codegenNode.arguments[3]).toMatch(
|
expect((root as any).children[0].codegenNode.patchFlag).toMatch(
|
||||||
PatchFlags.DYNAMIC_SLOTS + ''
|
PatchFlags.DYNAMIC_SLOTS + ''
|
||||||
)
|
)
|
||||||
expect(generate(root).code).toMatchSnapshot()
|
expect(generate(root).code).toMatchSnapshot()
|
||||||
@ -461,7 +564,7 @@ describe('compiler: transform component slots', () => {
|
|||||||
callee: CREATE_SLOTS,
|
callee: CREATE_SLOTS,
|
||||||
arguments: [
|
arguments: [
|
||||||
createObjectMatcher({
|
createObjectMatcher({
|
||||||
_compiled: `[true]`
|
_: `[1]`
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
type: NodeTypes.JS_ARRAY_EXPRESSION,
|
type: NodeTypes.JS_ARRAY_EXPRESSION,
|
||||||
@ -491,7 +594,7 @@ describe('compiler: transform component slots', () => {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
expect((root as any).children[0].codegenNode.arguments[3]).toMatch(
|
expect((root as any).children[0].codegenNode.patchFlag).toMatch(
|
||||||
PatchFlags.DYNAMIC_SLOTS + ''
|
PatchFlags.DYNAMIC_SLOTS + ''
|
||||||
)
|
)
|
||||||
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
|
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
|
||||||
@ -510,7 +613,7 @@ describe('compiler: transform component slots', () => {
|
|||||||
callee: CREATE_SLOTS,
|
callee: CREATE_SLOTS,
|
||||||
arguments: [
|
arguments: [
|
||||||
createObjectMatcher({
|
createObjectMatcher({
|
||||||
_compiled: `[true]`
|
_: `[1]`
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
type: NodeTypes.JS_ARRAY_EXPRESSION,
|
type: NodeTypes.JS_ARRAY_EXPRESSION,
|
||||||
@ -551,7 +654,7 @@ describe('compiler: transform component slots', () => {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
expect((root as any).children[0].codegenNode.arguments[3]).toMatch(
|
expect((root as any).children[0].codegenNode.patchFlag).toMatch(
|
||||||
PatchFlags.DYNAMIC_SLOTS + ''
|
PatchFlags.DYNAMIC_SLOTS + ''
|
||||||
)
|
)
|
||||||
expect(generate(root).code).toMatchSnapshot()
|
expect(generate(root).code).toMatchSnapshot()
|
||||||
@ -569,7 +672,7 @@ describe('compiler: transform component slots', () => {
|
|||||||
callee: CREATE_SLOTS,
|
callee: CREATE_SLOTS,
|
||||||
arguments: [
|
arguments: [
|
||||||
createObjectMatcher({
|
createObjectMatcher({
|
||||||
_compiled: `[true]`
|
_: `[1]`
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
type: NodeTypes.JS_ARRAY_EXPRESSION,
|
type: NodeTypes.JS_ARRAY_EXPRESSION,
|
||||||
@ -601,20 +704,20 @@ describe('compiler: transform component slots', () => {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
expect((root as any).children[0].codegenNode.arguments[3]).toMatch(
|
expect((root as any).children[0].codegenNode.patchFlag).toMatch(
|
||||||
PatchFlags.DYNAMIC_SLOTS + ''
|
PatchFlags.DYNAMIC_SLOTS + ''
|
||||||
)
|
)
|
||||||
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
|
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('errors', () => {
|
describe('errors', () => {
|
||||||
test('error on extraneous children w/ named slots', () => {
|
test('error on extraneous children w/ named default slot', () => {
|
||||||
const onError = jest.fn()
|
const onError = jest.fn()
|
||||||
const source = `<Comp><template #default>foo</template>bar</Comp>`
|
const source = `<Comp><template #default>foo</template>bar</Comp>`
|
||||||
parseWithSlots(source, { onError })
|
parseWithSlots(source, { onError })
|
||||||
const index = source.indexOf('bar')
|
const index = source.indexOf('bar')
|
||||||
expect(onError.mock.calls[0][0]).toMatchObject({
|
expect(onError.mock.calls[0][0]).toMatchObject({
|
||||||
code: ErrorCodes.X_V_SLOT_EXTRANEOUS_NON_SLOT_CHILDREN,
|
code: ErrorCodes.X_V_SLOT_EXTRANEOUS_DEFAULT_SLOT_CHILDREN,
|
||||||
loc: {
|
loc: {
|
||||||
source: `bar`,
|
source: `bar`,
|
||||||
start: {
|
start: {
|
||||||
@ -699,28 +802,5 @@ describe('compiler: transform component slots', () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test('error on named slot on component', () => {
|
|
||||||
const onError = jest.fn()
|
|
||||||
const source = `<Comp v-slot:foo>foo</Comp>`
|
|
||||||
parseWithSlots(source, { onError })
|
|
||||||
const index = source.indexOf('v-slot')
|
|
||||||
expect(onError.mock.calls[0][0]).toMatchObject({
|
|
||||||
code: ErrorCodes.X_V_SLOT_NAMED_SLOT_ON_COMPONENT,
|
|
||||||
loc: {
|
|
||||||
source: `v-slot:foo`,
|
|
||||||
start: {
|
|
||||||
offset: index,
|
|
||||||
line: 1,
|
|
||||||
column: index + 1
|
|
||||||
},
|
|
||||||
end: {
|
|
||||||
offset: index + 10,
|
|
||||||
line: 1,
|
|
||||||
column: index + 11
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
{
|
{
|
||||||
"name": "@vue/compiler-core",
|
"name": "@vue/compiler-core",
|
||||||
"version": "3.0.0-alpha.0",
|
"version": "3.0.0-alpha.11",
|
||||||
"description": "@vue/compiler-core",
|
"description": "@vue/compiler-core",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"module": "dist/compiler-core.esm-bundler.js",
|
"module": "dist/compiler-core.esm-bundler.js",
|
||||||
|
"types": "dist/compiler-core.d.ts",
|
||||||
"files": [
|
"files": [
|
||||||
"index.js",
|
"index.js",
|
||||||
"dist"
|
"dist"
|
||||||
],
|
],
|
||||||
"types": "dist/compiler-core.d.ts",
|
|
||||||
"buildOptions": {
|
"buildOptions": {
|
||||||
|
"name": "VueCompilerCore",
|
||||||
"formats": [
|
"formats": [
|
||||||
"esm-bundler",
|
"esm-bundler",
|
||||||
"cjs"
|
"cjs"
|
||||||
@ -17,7 +18,7 @@
|
|||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/vuejs/vue.git"
|
"url": "git+https://github.com/vuejs/vue-next.git"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"vue"
|
"vue"
|
||||||
@ -25,12 +26,14 @@
|
|||||||
"author": "Evan You",
|
"author": "Evan You",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/vuejs/vue/issues"
|
"url": "https://github.com/vuejs/vue-next/issues"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/vuejs/vue/tree/dev/packages/compiler-core#readme",
|
"homepage": "https://github.com/vuejs/vue-next/tree/master/packages/compiler-core#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"acorn": "^7.1.0",
|
"@vue/shared": "3.0.0-alpha.11",
|
||||||
|
"@babel/parser": "^7.8.6",
|
||||||
|
"@babel/types": "^7.8.6",
|
||||||
"estree-walker": "^0.8.1",
|
"estree-walker": "^0.8.1",
|
||||||
"source-map": "^0.7.3"
|
"source-map": "^0.6.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
import { isString } from '@vue/shared'
|
import { isString } from '@vue/shared'
|
||||||
import { ForParseResult } from './transforms/vFor'
|
import { ForParseResult } from './transforms/vFor'
|
||||||
import {
|
import {
|
||||||
CREATE_VNODE,
|
|
||||||
WITH_DIRECTIVES,
|
|
||||||
RENDER_SLOT,
|
RENDER_SLOT,
|
||||||
CREATE_SLOTS,
|
CREATE_SLOTS,
|
||||||
RENDER_LIST,
|
RENDER_LIST,
|
||||||
OPEN_BLOCK,
|
OPEN_BLOCK,
|
||||||
CREATE_BLOCK,
|
CREATE_BLOCK,
|
||||||
FRAGMENT
|
FRAGMENT,
|
||||||
|
CREATE_VNODE,
|
||||||
|
WITH_DIRECTIVES
|
||||||
} from './runtimeHelpers'
|
} from './runtimeHelpers'
|
||||||
import { PropsExpression } from './transforms/transformElement'
|
import { PropsExpression } from './transforms/transformElement'
|
||||||
import { ImportItem } from './transform'
|
import { ImportItem, TransformContext } from './transform'
|
||||||
|
|
||||||
// Vue template is a platform-agnostic superset of HTML (syntax only).
|
// Vue template is a platform-agnostic superset of HTML (syntax only).
|
||||||
// More namespaces like SVG and MathML are declared by platform specific
|
// More namespaces like SVG and MathML are declared by platform specific
|
||||||
@ -38,14 +38,22 @@ export const enum NodeTypes {
|
|||||||
FOR,
|
FOR,
|
||||||
TEXT_CALL,
|
TEXT_CALL,
|
||||||
// codegen
|
// codegen
|
||||||
|
VNODE_CALL,
|
||||||
JS_CALL_EXPRESSION,
|
JS_CALL_EXPRESSION,
|
||||||
JS_OBJECT_EXPRESSION,
|
JS_OBJECT_EXPRESSION,
|
||||||
JS_PROPERTY,
|
JS_PROPERTY,
|
||||||
JS_ARRAY_EXPRESSION,
|
JS_ARRAY_EXPRESSION,
|
||||||
JS_FUNCTION_EXPRESSION,
|
JS_FUNCTION_EXPRESSION,
|
||||||
JS_SEQUENCE_EXPRESSION,
|
|
||||||
JS_CONDITIONAL_EXPRESSION,
|
JS_CONDITIONAL_EXPRESSION,
|
||||||
JS_CACHE_EXPRESSION
|
JS_CACHE_EXPRESSION,
|
||||||
|
|
||||||
|
// ssr codegen
|
||||||
|
JS_BLOCK_STATEMENT,
|
||||||
|
JS_TEMPLATE_LITERAL,
|
||||||
|
JS_IF_STATEMENT,
|
||||||
|
JS_ASSIGNMENT_EXPRESSION,
|
||||||
|
JS_SEQUENCE_EXPRESSION,
|
||||||
|
JS_RETURN_STATEMENT
|
||||||
}
|
}
|
||||||
|
|
||||||
export const enum ElementTypes {
|
export const enum ElementTypes {
|
||||||
@ -85,6 +93,7 @@ export type TemplateChildNode =
|
|||||||
| TextNode
|
| TextNode
|
||||||
| CommentNode
|
| CommentNode
|
||||||
| IfNode
|
| IfNode
|
||||||
|
| IfBranchNode
|
||||||
| ForNode
|
| ForNode
|
||||||
| TextCallNode
|
| TextCallNode
|
||||||
|
|
||||||
@ -97,7 +106,9 @@ export interface RootNode extends Node {
|
|||||||
hoists: JSChildNode[]
|
hoists: JSChildNode[]
|
||||||
imports: ImportItem[]
|
imports: ImportItem[]
|
||||||
cached: number
|
cached: number
|
||||||
codegenNode: TemplateChildNode | JSChildNode | undefined
|
temps: number
|
||||||
|
ssrHelpers?: symbol[]
|
||||||
|
codegenNode?: TemplateChildNode | JSChildNode | BlockStatement | undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ElementNode =
|
export type ElementNode =
|
||||||
@ -114,35 +125,40 @@ export interface BaseElementNode extends Node {
|
|||||||
isSelfClosing: boolean
|
isSelfClosing: boolean
|
||||||
props: Array<AttributeNode | DirectiveNode>
|
props: Array<AttributeNode | DirectiveNode>
|
||||||
children: TemplateChildNode[]
|
children: TemplateChildNode[]
|
||||||
codegenNode:
|
|
||||||
| CallExpression
|
|
||||||
| SimpleExpressionNode
|
|
||||||
| CacheExpression
|
|
||||||
| undefined
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PlainElementNode extends BaseElementNode {
|
export interface PlainElementNode extends BaseElementNode {
|
||||||
tagType: ElementTypes.ELEMENT
|
tagType: ElementTypes.ELEMENT
|
||||||
codegenNode:
|
codegenNode:
|
||||||
| ElementCodegenNode
|
| VNodeCall
|
||||||
| undefined
|
|
||||||
| SimpleExpressionNode // when hoisted
|
| SimpleExpressionNode // when hoisted
|
||||||
| CacheExpression // when cached by v-once
|
| CacheExpression // when cached by v-once
|
||||||
|
| undefined
|
||||||
|
ssrCodegenNode?: TemplateLiteral
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ComponentNode extends BaseElementNode {
|
export interface ComponentNode extends BaseElementNode {
|
||||||
tagType: ElementTypes.COMPONENT
|
tagType: ElementTypes.COMPONENT
|
||||||
codegenNode: ComponentCodegenNode | undefined | CacheExpression // when cached by v-once
|
codegenNode:
|
||||||
|
| VNodeCall
|
||||||
|
| CacheExpression // when cached by v-once
|
||||||
|
| undefined
|
||||||
|
ssrCodegenNode?: CallExpression
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SlotOutletNode extends BaseElementNode {
|
export interface SlotOutletNode extends BaseElementNode {
|
||||||
tagType: ElementTypes.SLOT
|
tagType: ElementTypes.SLOT
|
||||||
codegenNode: SlotOutletCodegenNode | undefined | CacheExpression // when cached by v-once
|
codegenNode:
|
||||||
|
| RenderSlotCall
|
||||||
|
| CacheExpression // when cached by v-once
|
||||||
|
| undefined
|
||||||
|
ssrCodegenNode?: CallExpression
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TemplateNode extends BaseElementNode {
|
export interface TemplateNode extends BaseElementNode {
|
||||||
tagType: ElementTypes.TEMPLATE
|
tagType: ElementTypes.TEMPLATE
|
||||||
codegenNode: ElementCodegenNode | undefined | CacheExpression
|
// TemplateNode is a container type that always gets compiled away
|
||||||
|
codegenNode: undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TextNode extends Node {
|
export interface TextNode extends Node {
|
||||||
@ -190,6 +206,7 @@ export interface CompoundExpressionNode extends Node {
|
|||||||
type: NodeTypes.COMPOUND_EXPRESSION
|
type: NodeTypes.COMPOUND_EXPRESSION
|
||||||
children: (
|
children: (
|
||||||
| SimpleExpressionNode
|
| SimpleExpressionNode
|
||||||
|
| CompoundExpressionNode
|
||||||
| InterpolationNode
|
| InterpolationNode
|
||||||
| TextNode
|
| TextNode
|
||||||
| string
|
| string
|
||||||
@ -202,7 +219,7 @@ export interface CompoundExpressionNode extends Node {
|
|||||||
export interface IfNode extends Node {
|
export interface IfNode extends Node {
|
||||||
type: NodeTypes.IF
|
type: NodeTypes.IF
|
||||||
branches: IfBranchNode[]
|
branches: IfBranchNode[]
|
||||||
codegenNode: IfCodegenNode
|
codegenNode?: IfConditionalExpression
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IfBranchNode extends Node {
|
export interface IfBranchNode extends Node {
|
||||||
@ -217,28 +234,56 @@ export interface ForNode extends Node {
|
|||||||
valueAlias: ExpressionNode | undefined
|
valueAlias: ExpressionNode | undefined
|
||||||
keyAlias: ExpressionNode | undefined
|
keyAlias: ExpressionNode | undefined
|
||||||
objectIndexAlias: ExpressionNode | undefined
|
objectIndexAlias: ExpressionNode | undefined
|
||||||
|
parseResult: ForParseResult
|
||||||
children: TemplateChildNode[]
|
children: TemplateChildNode[]
|
||||||
codegenNode: ForCodegenNode
|
codegenNode?: ForCodegenNode
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TextCallNode extends Node {
|
export interface TextCallNode extends Node {
|
||||||
type: NodeTypes.TEXT_CALL
|
type: NodeTypes.TEXT_CALL
|
||||||
content: TextNode | InterpolationNode | CompoundExpressionNode
|
content: TextNode | InterpolationNode | CompoundExpressionNode
|
||||||
codegenNode: CallExpression
|
codegenNode: CallExpression | SimpleExpressionNode // when hoisted
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type TemplateTextChildNode =
|
||||||
|
| TextNode
|
||||||
|
| InterpolationNode
|
||||||
|
| CompoundExpressionNode
|
||||||
|
|
||||||
|
export interface VNodeCall extends Node {
|
||||||
|
type: NodeTypes.VNODE_CALL
|
||||||
|
tag: string | symbol | CallExpression
|
||||||
|
props: PropsExpression | undefined
|
||||||
|
children:
|
||||||
|
| TemplateChildNode[] // multiple children
|
||||||
|
| TemplateTextChildNode // single text child
|
||||||
|
| SlotsExpression // component slots
|
||||||
|
| ForRenderListExpression // v-for fragment call
|
||||||
|
| undefined
|
||||||
|
patchFlag: string | undefined
|
||||||
|
dynamicProps: string | undefined
|
||||||
|
directives: DirectiveArguments | undefined
|
||||||
|
isBlock: boolean
|
||||||
|
isForBlock: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
// JS Node Types ---------------------------------------------------------------
|
||||||
|
|
||||||
// We also include a number of JavaScript AST nodes for code generation.
|
// We also include a number of JavaScript AST nodes for code generation.
|
||||||
// The AST is an intentionally minimal subset just to meet the exact needs of
|
// The AST is an intentionally minimal subset just to meet the exact needs of
|
||||||
// Vue render function generation.
|
// Vue render function generation.
|
||||||
|
|
||||||
export type JSChildNode =
|
export type JSChildNode =
|
||||||
|
| VNodeCall
|
||||||
| CallExpression
|
| CallExpression
|
||||||
| ObjectExpression
|
| ObjectExpression
|
||||||
| ArrayExpression
|
| ArrayExpression
|
||||||
| ExpressionNode
|
| ExpressionNode
|
||||||
| FunctionExpression
|
| FunctionExpression
|
||||||
| ConditionalExpression
|
| ConditionalExpression
|
||||||
| SequenceExpression
|
|
||||||
| CacheExpression
|
| CacheExpression
|
||||||
|
| AssignmentExpression
|
||||||
|
| SequenceExpression
|
||||||
|
|
||||||
export interface CallExpression extends Node {
|
export interface CallExpression extends Node {
|
||||||
type: NodeTypes.JS_CALL_EXPRESSION
|
type: NodeTypes.JS_CALL_EXPRESSION
|
||||||
@ -247,6 +292,7 @@ export interface CallExpression extends Node {
|
|||||||
| string
|
| string
|
||||||
| symbol
|
| symbol
|
||||||
| JSChildNode
|
| JSChildNode
|
||||||
|
| SSRCodegenNode
|
||||||
| TemplateChildNode
|
| TemplateChildNode
|
||||||
| TemplateChildNode[])[]
|
| TemplateChildNode[])[]
|
||||||
}
|
}
|
||||||
@ -269,21 +315,20 @@ export interface ArrayExpression extends Node {
|
|||||||
|
|
||||||
export interface FunctionExpression extends Node {
|
export interface FunctionExpression extends Node {
|
||||||
type: NodeTypes.JS_FUNCTION_EXPRESSION
|
type: NodeTypes.JS_FUNCTION_EXPRESSION
|
||||||
params: ExpressionNode | ExpressionNode[] | undefined
|
params: ExpressionNode | string | (ExpressionNode | string)[] | undefined
|
||||||
returns: TemplateChildNode | TemplateChildNode[] | JSChildNode
|
returns?: TemplateChildNode | TemplateChildNode[] | JSChildNode
|
||||||
|
body?: BlockStatement | IfStatement
|
||||||
newline: boolean
|
newline: boolean
|
||||||
}
|
// so that codegen knows it needs to generate ScopeId wrapper
|
||||||
|
isSlot: boolean
|
||||||
export interface SequenceExpression extends Node {
|
|
||||||
type: NodeTypes.JS_SEQUENCE_EXPRESSION
|
|
||||||
expressions: JSChildNode[]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ConditionalExpression extends Node {
|
export interface ConditionalExpression extends Node {
|
||||||
type: NodeTypes.JS_CONDITIONAL_EXPRESSION
|
type: NodeTypes.JS_CONDITIONAL_EXPRESSION
|
||||||
test: ExpressionNode
|
test: JSChildNode
|
||||||
consequent: JSChildNode
|
consequent: JSChildNode
|
||||||
alternate: JSChildNode
|
alternate: JSChildNode
|
||||||
|
newline: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CacheExpression extends Node {
|
export interface CacheExpression extends Node {
|
||||||
@ -293,59 +338,76 @@ export interface CacheExpression extends Node {
|
|||||||
isVNode: boolean
|
isVNode: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SSR-specific Node Types -----------------------------------------------------
|
||||||
|
|
||||||
|
export type SSRCodegenNode =
|
||||||
|
| BlockStatement
|
||||||
|
| TemplateLiteral
|
||||||
|
| IfStatement
|
||||||
|
| AssignmentExpression
|
||||||
|
| ReturnStatement
|
||||||
|
| SequenceExpression
|
||||||
|
|
||||||
|
export interface BlockStatement extends Node {
|
||||||
|
type: NodeTypes.JS_BLOCK_STATEMENT
|
||||||
|
body: (JSChildNode | IfStatement)[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TemplateLiteral extends Node {
|
||||||
|
type: NodeTypes.JS_TEMPLATE_LITERAL
|
||||||
|
elements: (string | JSChildNode)[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IfStatement extends Node {
|
||||||
|
type: NodeTypes.JS_IF_STATEMENT
|
||||||
|
test: ExpressionNode
|
||||||
|
consequent: BlockStatement
|
||||||
|
alternate: IfStatement | BlockStatement | ReturnStatement | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AssignmentExpression extends Node {
|
||||||
|
type: NodeTypes.JS_ASSIGNMENT_EXPRESSION
|
||||||
|
left: SimpleExpressionNode
|
||||||
|
right: JSChildNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SequenceExpression extends Node {
|
||||||
|
type: NodeTypes.JS_SEQUENCE_EXPRESSION
|
||||||
|
expressions: JSChildNode[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ReturnStatement extends Node {
|
||||||
|
type: NodeTypes.JS_RETURN_STATEMENT
|
||||||
|
returns: TemplateChildNode | TemplateChildNode[] | JSChildNode
|
||||||
|
}
|
||||||
|
|
||||||
// Codegen Node Types ----------------------------------------------------------
|
// Codegen Node Types ----------------------------------------------------------
|
||||||
|
|
||||||
// createVNode(...)
|
export interface DirectiveArguments extends ArrayExpression {
|
||||||
export interface PlainElementCodegenNode extends CallExpression {
|
elements: DirectiveArgumentNode[]
|
||||||
callee: typeof CREATE_VNODE | typeof CREATE_BLOCK
|
|
||||||
arguments: // tag, props, children, patchFlag, dynamicProps
|
|
||||||
| [string | symbol]
|
|
||||||
| [string | symbol, PropsExpression]
|
|
||||||
| [string | symbol, 'null' | PropsExpression, TemplateChildNode[]]
|
|
||||||
| [
|
|
||||||
string | symbol,
|
|
||||||
'null' | PropsExpression,
|
|
||||||
'null' | TemplateChildNode[],
|
|
||||||
string
|
|
||||||
]
|
|
||||||
| [
|
|
||||||
string | symbol,
|
|
||||||
'null' | PropsExpression,
|
|
||||||
'null' | TemplateChildNode[],
|
|
||||||
string,
|
|
||||||
string
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ElementCodegenNode =
|
export interface DirectiveArgumentNode extends ArrayExpression {
|
||||||
| PlainElementCodegenNode
|
elements: // dir, exp, arg, modifiers
|
||||||
| CodegenNodeWithDirective<PlainElementCodegenNode>
|
| [string]
|
||||||
|
| [string, ExpressionNode]
|
||||||
// createVNode(...)
|
| [string, ExpressionNode, ExpressionNode]
|
||||||
export interface PlainComponentCodegenNode extends CallExpression {
|
| [string, ExpressionNode, ExpressionNode, ObjectExpression]
|
||||||
callee: typeof CREATE_VNODE | typeof CREATE_BLOCK
|
|
||||||
arguments: // Comp, props, slots, patchFlag, dynamicProps
|
|
||||||
| [string | symbol]
|
|
||||||
| [string | symbol, PropsExpression]
|
|
||||||
| [string | symbol, 'null' | PropsExpression, SlotsExpression]
|
|
||||||
| [
|
|
||||||
string | symbol,
|
|
||||||
'null' | PropsExpression,
|
|
||||||
'null' | SlotsExpression,
|
|
||||||
string
|
|
||||||
]
|
|
||||||
| [
|
|
||||||
string | symbol,
|
|
||||||
'null' | PropsExpression,
|
|
||||||
'null' | SlotsExpression,
|
|
||||||
string,
|
|
||||||
string
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ComponentCodegenNode =
|
// renderSlot(...)
|
||||||
| PlainComponentCodegenNode
|
export interface RenderSlotCall extends CallExpression {
|
||||||
| CodegenNodeWithDirective<PlainComponentCodegenNode>
|
callee: typeof RENDER_SLOT
|
||||||
|
arguments: // $slots, name, props, fallback
|
||||||
|
| [string, string | ExpressionNode]
|
||||||
|
| [string, string | ExpressionNode, PropsExpression]
|
||||||
|
| [
|
||||||
|
string,
|
||||||
|
string | ExpressionNode,
|
||||||
|
PropsExpression | '{}',
|
||||||
|
TemplateChildNode[]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
export type SlotsExpression = SlotsObjectExpression | DynamicSlotsExpression
|
export type SlotsExpression = SlotsObjectExpression | DynamicSlotsExpression
|
||||||
|
|
||||||
@ -397,63 +459,20 @@ export interface DynamicSlotFnProperty extends Property {
|
|||||||
value: SlotFunctionExpression
|
value: SlotFunctionExpression
|
||||||
}
|
}
|
||||||
|
|
||||||
// withDirectives(createVNode(...), [
|
export type BlockCodegenNode = VNodeCall | RenderSlotCall
|
||||||
// [_directive_foo, someValue],
|
|
||||||
// [_directive_bar, someValue, "arg", { mod: true }]
|
|
||||||
// ])
|
|
||||||
export interface CodegenNodeWithDirective<T extends CallExpression>
|
|
||||||
extends CallExpression {
|
|
||||||
callee: typeof WITH_DIRECTIVES
|
|
||||||
arguments: [T, DirectiveArguments]
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DirectiveArguments extends ArrayExpression {
|
|
||||||
elements: DirectiveArgumentNode[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DirectiveArgumentNode extends ArrayExpression {
|
|
||||||
elements: // dir, exp, arg, modifiers
|
|
||||||
| [string]
|
|
||||||
| [string, ExpressionNode]
|
|
||||||
| [string, ExpressionNode, ExpressionNode]
|
|
||||||
| [string, ExpressionNode, ExpressionNode, ObjectExpression]
|
|
||||||
}
|
|
||||||
|
|
||||||
// renderSlot(...)
|
|
||||||
export interface SlotOutletCodegenNode extends CallExpression {
|
|
||||||
callee: typeof RENDER_SLOT
|
|
||||||
arguments: // $slots, name, props, fallback
|
|
||||||
| [string, string | ExpressionNode]
|
|
||||||
| [string, string | ExpressionNode, PropsExpression]
|
|
||||||
| [
|
|
||||||
string,
|
|
||||||
string | ExpressionNode,
|
|
||||||
PropsExpression | '{}',
|
|
||||||
TemplateChildNode[]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
export type BlockCodegenNode =
|
|
||||||
| ElementCodegenNode
|
|
||||||
| ComponentCodegenNode
|
|
||||||
| SlotOutletCodegenNode
|
|
||||||
|
|
||||||
export interface IfCodegenNode extends SequenceExpression {
|
|
||||||
expressions: [OpenBlockExpression, IfConditionalExpression]
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IfConditionalExpression extends ConditionalExpression {
|
export interface IfConditionalExpression extends ConditionalExpression {
|
||||||
consequent: BlockCodegenNode
|
consequent: BlockCodegenNode
|
||||||
alternate: BlockCodegenNode | IfConditionalExpression
|
alternate: BlockCodegenNode | IfConditionalExpression
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ForCodegenNode extends SequenceExpression {
|
export interface ForCodegenNode extends VNodeCall {
|
||||||
expressions: [OpenBlockExpression, ForBlockCodegenNode]
|
isBlock: true
|
||||||
}
|
tag: typeof FRAGMENT
|
||||||
|
props: undefined
|
||||||
export interface ForBlockCodegenNode extends CallExpression {
|
children: ForRenderListExpression
|
||||||
callee: typeof CREATE_BLOCK
|
patchFlag: string
|
||||||
arguments: [typeof FRAGMENT, 'null', ForRenderListExpression, string]
|
isForBlock: true
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ForRenderListExpression extends CallExpression {
|
export interface ForRenderListExpression extends CallExpression {
|
||||||
@ -465,11 +484,6 @@ export interface ForIteratorExpression extends FunctionExpression {
|
|||||||
returns: BlockCodegenNode
|
returns: BlockCodegenNode
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OpenBlockExpression extends CallExpression {
|
|
||||||
callee: typeof OPEN_BLOCK
|
|
||||||
arguments: []
|
|
||||||
}
|
|
||||||
|
|
||||||
// AST Utilities ---------------------------------------------------------------
|
// AST Utilities ---------------------------------------------------------------
|
||||||
|
|
||||||
// Some expressions, e.g. sequence and conditional expressions, are never
|
// Some expressions, e.g. sequence and conditional expressions, are never
|
||||||
@ -481,6 +495,63 @@ export const locStub: SourceLocation = {
|
|||||||
end: { line: 1, column: 1, offset: 0 }
|
end: { line: 1, column: 1, offset: 0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createRoot(
|
||||||
|
children: TemplateChildNode[],
|
||||||
|
loc = locStub
|
||||||
|
): RootNode {
|
||||||
|
return {
|
||||||
|
type: NodeTypes.ROOT,
|
||||||
|
children,
|
||||||
|
helpers: [],
|
||||||
|
components: [],
|
||||||
|
directives: [],
|
||||||
|
hoists: [],
|
||||||
|
imports: [],
|
||||||
|
cached: 0,
|
||||||
|
temps: 0,
|
||||||
|
codegenNode: undefined,
|
||||||
|
loc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createVNodeCall(
|
||||||
|
context: TransformContext | null,
|
||||||
|
tag: VNodeCall['tag'],
|
||||||
|
props?: VNodeCall['props'],
|
||||||
|
children?: VNodeCall['children'],
|
||||||
|
patchFlag?: VNodeCall['patchFlag'],
|
||||||
|
dynamicProps?: VNodeCall['dynamicProps'],
|
||||||
|
directives?: VNodeCall['directives'],
|
||||||
|
isBlock: VNodeCall['isBlock'] = false,
|
||||||
|
isForBlock: VNodeCall['isForBlock'] = false,
|
||||||
|
loc = locStub
|
||||||
|
): VNodeCall {
|
||||||
|
if (context) {
|
||||||
|
if (isBlock) {
|
||||||
|
context.helper(OPEN_BLOCK)
|
||||||
|
context.helper(CREATE_BLOCK)
|
||||||
|
} else {
|
||||||
|
context.helper(CREATE_VNODE)
|
||||||
|
}
|
||||||
|
if (directives) {
|
||||||
|
context.helper(WITH_DIRECTIVES)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: NodeTypes.VNODE_CALL,
|
||||||
|
tag,
|
||||||
|
props,
|
||||||
|
children,
|
||||||
|
patchFlag,
|
||||||
|
dynamicProps,
|
||||||
|
directives,
|
||||||
|
isBlock,
|
||||||
|
isForBlock,
|
||||||
|
loc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function createArrayExpression(
|
export function createArrayExpression(
|
||||||
elements: ArrayExpression['elements'],
|
elements: ArrayExpression['elements'],
|
||||||
loc: SourceLocation = locStub
|
loc: SourceLocation = locStub
|
||||||
@ -554,15 +625,9 @@ export function createCompoundExpression(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type InferCodegenNodeType<T> = T extends
|
type InferCodegenNodeType<T> = T extends typeof RENDER_SLOT
|
||||||
| typeof CREATE_VNODE
|
? RenderSlotCall
|
||||||
| typeof CREATE_BLOCK
|
: CallExpression
|
||||||
? PlainElementCodegenNode | PlainComponentCodegenNode
|
|
||||||
: T extends typeof WITH_DIRECTIVES
|
|
||||||
?
|
|
||||||
| CodegenNodeWithDirective<PlainElementCodegenNode>
|
|
||||||
| CodegenNodeWithDirective<PlainComponentCodegenNode>
|
|
||||||
: T extends typeof RENDER_SLOT ? SlotOutletCodegenNode : CallExpression
|
|
||||||
|
|
||||||
export function createCallExpression<T extends CallExpression['callee']>(
|
export function createCallExpression<T extends CallExpression['callee']>(
|
||||||
callee: T,
|
callee: T,
|
||||||
@ -579,8 +644,9 @@ export function createCallExpression<T extends CallExpression['callee']>(
|
|||||||
|
|
||||||
export function createFunctionExpression(
|
export function createFunctionExpression(
|
||||||
params: FunctionExpression['params'],
|
params: FunctionExpression['params'],
|
||||||
returns: FunctionExpression['returns'],
|
returns: FunctionExpression['returns'] = undefined,
|
||||||
newline: boolean = false,
|
newline: boolean = false,
|
||||||
|
isSlot: boolean = false,
|
||||||
loc: SourceLocation = locStub
|
loc: SourceLocation = locStub
|
||||||
): FunctionExpression {
|
): FunctionExpression {
|
||||||
return {
|
return {
|
||||||
@ -588,30 +654,23 @@ export function createFunctionExpression(
|
|||||||
params,
|
params,
|
||||||
returns,
|
returns,
|
||||||
newline,
|
newline,
|
||||||
|
isSlot,
|
||||||
loc
|
loc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createSequenceExpression(
|
|
||||||
expressions: SequenceExpression['expressions']
|
|
||||||
): SequenceExpression {
|
|
||||||
return {
|
|
||||||
type: NodeTypes.JS_SEQUENCE_EXPRESSION,
|
|
||||||
expressions,
|
|
||||||
loc: locStub
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createConditionalExpression(
|
export function createConditionalExpression(
|
||||||
test: ConditionalExpression['test'],
|
test: ConditionalExpression['test'],
|
||||||
consequent: ConditionalExpression['consequent'],
|
consequent: ConditionalExpression['consequent'],
|
||||||
alternate: ConditionalExpression['alternate']
|
alternate: ConditionalExpression['alternate'],
|
||||||
|
newline = true
|
||||||
): ConditionalExpression {
|
): ConditionalExpression {
|
||||||
return {
|
return {
|
||||||
type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
|
type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
|
||||||
test,
|
test,
|
||||||
consequent,
|
consequent,
|
||||||
alternate,
|
alternate,
|
||||||
|
newline,
|
||||||
loc: locStub
|
loc: locStub
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -629,3 +688,69 @@ export function createCacheExpression(
|
|||||||
loc: locStub
|
loc: locStub
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createBlockStatement(
|
||||||
|
body: BlockStatement['body']
|
||||||
|
): BlockStatement {
|
||||||
|
return {
|
||||||
|
type: NodeTypes.JS_BLOCK_STATEMENT,
|
||||||
|
body,
|
||||||
|
loc: locStub
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createTemplateLiteral(
|
||||||
|
elements: TemplateLiteral['elements']
|
||||||
|
): TemplateLiteral {
|
||||||
|
return {
|
||||||
|
type: NodeTypes.JS_TEMPLATE_LITERAL,
|
||||||
|
elements,
|
||||||
|
loc: locStub
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createIfStatement(
|
||||||
|
test: IfStatement['test'],
|
||||||
|
consequent: IfStatement['consequent'],
|
||||||
|
alternate?: IfStatement['alternate']
|
||||||
|
): IfStatement {
|
||||||
|
return {
|
||||||
|
type: NodeTypes.JS_IF_STATEMENT,
|
||||||
|
test,
|
||||||
|
consequent,
|
||||||
|
alternate,
|
||||||
|
loc: locStub
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createAssignmentExpression(
|
||||||
|
left: AssignmentExpression['left'],
|
||||||
|
right: AssignmentExpression['right']
|
||||||
|
): AssignmentExpression {
|
||||||
|
return {
|
||||||
|
type: NodeTypes.JS_ASSIGNMENT_EXPRESSION,
|
||||||
|
left,
|
||||||
|
right,
|
||||||
|
loc: locStub
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createSequenceExpression(
|
||||||
|
expressions: SequenceExpression['expressions']
|
||||||
|
): SequenceExpression {
|
||||||
|
return {
|
||||||
|
type: NodeTypes.JS_SEQUENCE_EXPRESSION,
|
||||||
|
expressions,
|
||||||
|
loc: locStub
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createReturnStatement(
|
||||||
|
returns: ReturnStatement['returns']
|
||||||
|
): ReturnStatement {
|
||||||
|
return {
|
||||||
|
type: NodeTypes.JS_RETURN_STATEMENT,
|
||||||
|
returns,
|
||||||
|
loc: locStub
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -10,15 +10,21 @@ import {
|
|||||||
CallExpression,
|
CallExpression,
|
||||||
ArrayExpression,
|
ArrayExpression,
|
||||||
ObjectExpression,
|
ObjectExpression,
|
||||||
SourceLocation,
|
|
||||||
Position,
|
Position,
|
||||||
InterpolationNode,
|
InterpolationNode,
|
||||||
CompoundExpressionNode,
|
CompoundExpressionNode,
|
||||||
SimpleExpressionNode,
|
SimpleExpressionNode,
|
||||||
FunctionExpression,
|
FunctionExpression,
|
||||||
SequenceExpression,
|
|
||||||
ConditionalExpression,
|
ConditionalExpression,
|
||||||
CacheExpression
|
CacheExpression,
|
||||||
|
locStub,
|
||||||
|
SSRCodegenNode,
|
||||||
|
TemplateLiteral,
|
||||||
|
IfStatement,
|
||||||
|
AssignmentExpression,
|
||||||
|
ReturnStatement,
|
||||||
|
VNodeCall,
|
||||||
|
SequenceExpression
|
||||||
} from './ast'
|
} from './ast'
|
||||||
import { SourceMapGenerator, RawSourceMap } from 'source-map'
|
import { SourceMapGenerator, RawSourceMap } from 'source-map'
|
||||||
import {
|
import {
|
||||||
@ -31,17 +37,25 @@ import {
|
|||||||
import { isString, isArray, isSymbol } from '@vue/shared'
|
import { isString, isArray, isSymbol } from '@vue/shared'
|
||||||
import {
|
import {
|
||||||
helperNameMap,
|
helperNameMap,
|
||||||
TO_STRING,
|
TO_DISPLAY_STRING,
|
||||||
CREATE_VNODE,
|
CREATE_VNODE,
|
||||||
RESOLVE_COMPONENT,
|
RESOLVE_COMPONENT,
|
||||||
RESOLVE_DIRECTIVE,
|
RESOLVE_DIRECTIVE,
|
||||||
SET_BLOCK_TRACKING,
|
SET_BLOCK_TRACKING,
|
||||||
CREATE_COMMENT,
|
CREATE_COMMENT,
|
||||||
CREATE_TEXT
|
CREATE_TEXT,
|
||||||
|
PUSH_SCOPE_ID,
|
||||||
|
POP_SCOPE_ID,
|
||||||
|
WITH_SCOPE_ID,
|
||||||
|
WITH_DIRECTIVES,
|
||||||
|
CREATE_BLOCK,
|
||||||
|
OPEN_BLOCK,
|
||||||
|
CREATE_STATIC,
|
||||||
|
WITH_CTX
|
||||||
} from './runtimeHelpers'
|
} from './runtimeHelpers'
|
||||||
import { ImportItem } from './transform'
|
import { ImportItem } from './transform'
|
||||||
|
|
||||||
type CodegenNode = TemplateChildNode | JSChildNode
|
type CodegenNode = TemplateChildNode | JSChildNode | SSRCodegenNode
|
||||||
|
|
||||||
export interface CodegenResult {
|
export interface CodegenResult {
|
||||||
code: string
|
code: string
|
||||||
@ -58,8 +72,7 @@ export interface CodegenContext extends Required<CodegenOptions> {
|
|||||||
indentLevel: number
|
indentLevel: number
|
||||||
map?: SourceMapGenerator
|
map?: SourceMapGenerator
|
||||||
helper(key: symbol): string
|
helper(key: symbol): string
|
||||||
push(code: string, node?: CodegenNode, openOnly?: boolean): void
|
push(code: string, node?: CodegenNode): void
|
||||||
resetMapping(loc: SourceLocation): void
|
|
||||||
indent(): void
|
indent(): void
|
||||||
deindent(withoutNewLine?: boolean): void
|
deindent(withoutNewLine?: boolean): void
|
||||||
newline(): void
|
newline(): void
|
||||||
@ -71,7 +84,12 @@ function createCodegenContext(
|
|||||||
mode = 'function',
|
mode = 'function',
|
||||||
prefixIdentifiers = mode === 'module',
|
prefixIdentifiers = mode === 'module',
|
||||||
sourceMap = false,
|
sourceMap = false,
|
||||||
filename = `template.vue.html`
|
filename = `template.vue.html`,
|
||||||
|
scopeId = null,
|
||||||
|
optimizeBindings = false,
|
||||||
|
runtimeGlobalName = `Vue`,
|
||||||
|
runtimeModuleName = `vue`,
|
||||||
|
ssr = false
|
||||||
}: CodegenOptions
|
}: CodegenOptions
|
||||||
): CodegenContext {
|
): CodegenContext {
|
||||||
const context: CodegenContext = {
|
const context: CodegenContext = {
|
||||||
@ -79,24 +97,22 @@ function createCodegenContext(
|
|||||||
prefixIdentifiers,
|
prefixIdentifiers,
|
||||||
sourceMap,
|
sourceMap,
|
||||||
filename,
|
filename,
|
||||||
|
scopeId,
|
||||||
|
optimizeBindings,
|
||||||
|
runtimeGlobalName,
|
||||||
|
runtimeModuleName,
|
||||||
|
ssr,
|
||||||
source: ast.loc.source,
|
source: ast.loc.source,
|
||||||
code: ``,
|
code: ``,
|
||||||
column: 1,
|
column: 1,
|
||||||
line: 1,
|
line: 1,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
indentLevel: 0,
|
indentLevel: 0,
|
||||||
|
map: undefined,
|
||||||
// lazy require source-map implementation, only in non-browser builds!
|
|
||||||
map:
|
|
||||||
__BROWSER__ || !sourceMap
|
|
||||||
? undefined
|
|
||||||
: new (loadDep('source-map')).SourceMapGenerator(),
|
|
||||||
|
|
||||||
helper(key) {
|
helper(key) {
|
||||||
const name = helperNameMap[key]
|
return `_${helperNameMap[key]}`
|
||||||
return prefixIdentifiers ? name : `_${name}`
|
|
||||||
},
|
},
|
||||||
push(code, node, openOnly) {
|
push(code, node) {
|
||||||
context.code += code
|
context.code += code
|
||||||
if (!__BROWSER__ && context.map) {
|
if (!__BROWSER__ && context.map) {
|
||||||
if (node) {
|
if (node) {
|
||||||
@ -110,16 +126,11 @@ function createCodegenContext(
|
|||||||
addMapping(node.loc.start, name)
|
addMapping(node.loc.start, name)
|
||||||
}
|
}
|
||||||
advancePositionWithMutation(context, code)
|
advancePositionWithMutation(context, code)
|
||||||
if (node && !openOnly) {
|
if (node && node.loc !== locStub) {
|
||||||
addMapping(node.loc.end)
|
addMapping(node.loc.end)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
resetMapping(loc: SourceLocation) {
|
|
||||||
if (!__BROWSER__ && context.map) {
|
|
||||||
addMapping(loc.start)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
indent() {
|
indent() {
|
||||||
newline(++context.indentLevel)
|
newline(++context.indentLevel)
|
||||||
},
|
},
|
||||||
@ -154,9 +165,12 @@ function createCodegenContext(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!__BROWSER__ && context.map) {
|
if (!__BROWSER__ && sourceMap) {
|
||||||
context.map.setSourceContent(filename, context.source)
|
// lazy require source-map implementation, only in non-browser builds
|
||||||
|
context.map = new (loadDep('source-map')).SourceMapGenerator()
|
||||||
|
context.map!.setSourceContent(filename, context.source)
|
||||||
}
|
}
|
||||||
|
|
||||||
return context
|
return context
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,63 +182,37 @@ export function generate(
|
|||||||
const {
|
const {
|
||||||
mode,
|
mode,
|
||||||
push,
|
push,
|
||||||
helper,
|
|
||||||
prefixIdentifiers,
|
prefixIdentifiers,
|
||||||
indent,
|
indent,
|
||||||
deindent,
|
deindent,
|
||||||
newline
|
newline,
|
||||||
|
scopeId,
|
||||||
|
ssr
|
||||||
} = context
|
} = context
|
||||||
const hasHelpers = ast.helpers.length > 0
|
const hasHelpers = ast.helpers.length > 0
|
||||||
const useWithBlock = !prefixIdentifiers && mode !== 'module'
|
const useWithBlock = !prefixIdentifiers && mode !== 'module'
|
||||||
|
const genScopeId = !__BROWSER__ && scopeId != null && mode === 'module'
|
||||||
|
|
||||||
// preambles
|
// preambles
|
||||||
if (mode === 'function') {
|
if (!__BROWSER__ && mode === 'module') {
|
||||||
// Generate const declaration for helpers
|
genModulePreamble(ast, context, genScopeId)
|
||||||
// In prefix mode, we place the const declaration at top so it's done
|
|
||||||
// only once; But if we not prefixing, we place the declaration inside the
|
|
||||||
// with block so it doesn't incur the `in` check cost for every helper access.
|
|
||||||
if (hasHelpers) {
|
|
||||||
if (prefixIdentifiers) {
|
|
||||||
push(`const { ${ast.helpers.map(helper).join(', ')} } = Vue\n`)
|
|
||||||
} else {
|
|
||||||
// "with" mode.
|
|
||||||
// save Vue in a separate variable to avoid collision
|
|
||||||
push(`const _Vue = Vue\n`)
|
|
||||||
// in "with" mode, helpers are declared inside the with block to avoid
|
|
||||||
// has check cost, but hoists are lifted out of the function - we need
|
|
||||||
// to provide the helper here.
|
|
||||||
if (ast.hoists.length) {
|
|
||||||
const staticHelpers = [CREATE_VNODE, CREATE_COMMENT, CREATE_TEXT]
|
|
||||||
.filter(helper => ast.helpers.includes(helper))
|
|
||||||
.map(s => `${helperNameMap[s]}: _${helperNameMap[s]}`)
|
|
||||||
.join(', ')
|
|
||||||
push(`const { ${staticHelpers} } = Vue\n`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
genHoists(ast.hoists, context)
|
|
||||||
newline()
|
|
||||||
push(`return `)
|
|
||||||
} else {
|
} else {
|
||||||
// generate import statements for helpers
|
genFunctionPreamble(ast, context)
|
||||||
if (hasHelpers) {
|
|
||||||
push(`import { ${ast.helpers.map(helper).join(', ')} } from "vue"\n`)
|
|
||||||
}
|
|
||||||
if (ast.imports.length) {
|
|
||||||
genImports(ast.imports, context)
|
|
||||||
newline()
|
|
||||||
}
|
|
||||||
genHoists(ast.hoists, context)
|
|
||||||
newline()
|
|
||||||
push(`export default `)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// enter render function
|
// enter render function
|
||||||
push(`function render() {`)
|
if (genScopeId && !ssr) {
|
||||||
|
push(`const render = _withId(`)
|
||||||
|
}
|
||||||
|
if (!ssr) {
|
||||||
|
push(`function render(_ctx, _cache) {`)
|
||||||
|
} else {
|
||||||
|
push(`function ssrRender(_ctx, _push, _parent) {`)
|
||||||
|
}
|
||||||
indent()
|
indent()
|
||||||
|
|
||||||
if (useWithBlock) {
|
if (useWithBlock) {
|
||||||
push(`with (this) {`)
|
push(`with (_ctx) {`)
|
||||||
indent()
|
indent()
|
||||||
// function mode const declarations should be inside with block
|
// function mode const declarations should be inside with block
|
||||||
// also they should be renamed to avoid collision with user properties
|
// also they should be renamed to avoid collision with user properties
|
||||||
@ -234,35 +222,39 @@ export function generate(
|
|||||||
.map(s => `${helperNameMap[s]}: _${helperNameMap[s]}`)
|
.map(s => `${helperNameMap[s]}: _${helperNameMap[s]}`)
|
||||||
.join(', ')} } = _Vue`
|
.join(', ')} } = _Vue`
|
||||||
)
|
)
|
||||||
newline()
|
push(`\n`)
|
||||||
if (ast.cached > 0) {
|
|
||||||
push(`const _cache = $cache`)
|
|
||||||
newline()
|
|
||||||
}
|
|
||||||
newline()
|
newline()
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
push(`const _ctx = this`)
|
|
||||||
if (ast.cached > 0) {
|
|
||||||
newline()
|
|
||||||
push(`const _cache = _ctx.$cache`)
|
|
||||||
}
|
|
||||||
newline()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// generate asset resolution statements
|
// generate asset resolution statements
|
||||||
if (ast.components.length) {
|
if (ast.components.length) {
|
||||||
genAssets(ast.components, 'component', context)
|
genAssets(ast.components, 'component', context)
|
||||||
|
if (ast.directives.length || ast.temps > 0) {
|
||||||
|
newline()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (ast.directives.length) {
|
if (ast.directives.length) {
|
||||||
genAssets(ast.directives, 'directive', context)
|
genAssets(ast.directives, 'directive', context)
|
||||||
|
if (ast.temps > 0) {
|
||||||
|
newline()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (ast.components.length || ast.directives.length) {
|
if (ast.temps > 0) {
|
||||||
|
push(`let `)
|
||||||
|
for (let i = 0; i < ast.temps; i++) {
|
||||||
|
push(`${i > 0 ? `, ` : ``}_temp${i}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ast.components.length || ast.directives.length || ast.temps) {
|
||||||
|
push(`\n`)
|
||||||
newline()
|
newline()
|
||||||
}
|
}
|
||||||
|
|
||||||
// generate the VNode tree expression
|
// generate the VNode tree expression
|
||||||
push(`return `)
|
if (!ssr) {
|
||||||
|
push(`return `)
|
||||||
|
}
|
||||||
if (ast.codegenNode) {
|
if (ast.codegenNode) {
|
||||||
genNode(ast.codegenNode, context)
|
genNode(ast.codegenNode, context)
|
||||||
} else {
|
} else {
|
||||||
@ -276,27 +268,164 @@ export function generate(
|
|||||||
|
|
||||||
deindent()
|
deindent()
|
||||||
push(`}`)
|
push(`}`)
|
||||||
|
|
||||||
|
if (genScopeId && !ssr) {
|
||||||
|
push(`)`)
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ast,
|
ast,
|
||||||
code: context.code,
|
code: context.code,
|
||||||
map: context.map ? context.map.toJSON() : undefined
|
// SourceMapGenerator does have toJSON() method but it's not in the types
|
||||||
|
map: context.map ? (context.map as any).toJSON() : undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function genFunctionPreamble(ast: RootNode, context: CodegenContext) {
|
||||||
|
const {
|
||||||
|
ssr,
|
||||||
|
prefixIdentifiers,
|
||||||
|
push,
|
||||||
|
newline,
|
||||||
|
runtimeModuleName,
|
||||||
|
runtimeGlobalName
|
||||||
|
} = context
|
||||||
|
const VueBinding =
|
||||||
|
!__BROWSER__ && ssr
|
||||||
|
? `require(${JSON.stringify(runtimeModuleName)})`
|
||||||
|
: runtimeGlobalName
|
||||||
|
const aliasHelper = (s: symbol) => `${helperNameMap[s]}: _${helperNameMap[s]}`
|
||||||
|
// Generate const declaration for helpers
|
||||||
|
// In prefix mode, we place the const declaration at top so it's done
|
||||||
|
// only once; But if we not prefixing, we place the declaration inside the
|
||||||
|
// with block so it doesn't incur the `in` check cost for every helper access.
|
||||||
|
if (ast.helpers.length > 0) {
|
||||||
|
if (!__BROWSER__ && prefixIdentifiers) {
|
||||||
|
push(
|
||||||
|
`const { ${ast.helpers.map(aliasHelper).join(', ')} } = ${VueBinding}\n`
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// "with" mode.
|
||||||
|
// save Vue in a separate variable to avoid collision
|
||||||
|
push(`const _Vue = ${VueBinding}\n`)
|
||||||
|
// in "with" mode, helpers are declared inside the with block to avoid
|
||||||
|
// has check cost, but hoists are lifted out of the function - we need
|
||||||
|
// to provide the helper here.
|
||||||
|
if (ast.hoists.length) {
|
||||||
|
const staticHelpers = [
|
||||||
|
CREATE_VNODE,
|
||||||
|
CREATE_COMMENT,
|
||||||
|
CREATE_TEXT,
|
||||||
|
CREATE_STATIC
|
||||||
|
]
|
||||||
|
.filter(helper => ast.helpers.includes(helper))
|
||||||
|
.map(aliasHelper)
|
||||||
|
.join(', ')
|
||||||
|
push(`const { ${staticHelpers} } = _Vue\n`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// generate variables for ssr helpers
|
||||||
|
if (!__BROWSER__ && ast.ssrHelpers && ast.ssrHelpers.length) {
|
||||||
|
// ssr guaruntees prefixIdentifier: true
|
||||||
|
push(
|
||||||
|
`const { ${ast.ssrHelpers
|
||||||
|
.map(aliasHelper)
|
||||||
|
.join(', ')} } = require("@vue/server-renderer")\n`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
genHoists(ast.hoists, context)
|
||||||
|
newline()
|
||||||
|
push(`return `)
|
||||||
|
}
|
||||||
|
|
||||||
|
function genModulePreamble(
|
||||||
|
ast: RootNode,
|
||||||
|
context: CodegenContext,
|
||||||
|
genScopeId: boolean
|
||||||
|
) {
|
||||||
|
const {
|
||||||
|
push,
|
||||||
|
helper,
|
||||||
|
newline,
|
||||||
|
scopeId,
|
||||||
|
optimizeBindings,
|
||||||
|
runtimeModuleName
|
||||||
|
} = context
|
||||||
|
|
||||||
|
if (genScopeId) {
|
||||||
|
ast.helpers.push(WITH_SCOPE_ID)
|
||||||
|
if (ast.hoists.length) {
|
||||||
|
ast.helpers.push(PUSH_SCOPE_ID, POP_SCOPE_ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate import statements for helpers
|
||||||
|
if (ast.helpers.length) {
|
||||||
|
if (optimizeBindings) {
|
||||||
|
// when bundled with webpack with code-split, calling an import binding
|
||||||
|
// as a function leads to it being wrapped with `Object(a.b)` or `(0,a.b)`,
|
||||||
|
// incurring both payload size increase and potential perf overhead.
|
||||||
|
// therefore we assign the imports to vairables (which is a constant ~50b
|
||||||
|
// cost per-component instead of scaling with template size)
|
||||||
|
push(
|
||||||
|
`import { ${ast.helpers
|
||||||
|
.map(s => helperNameMap[s])
|
||||||
|
.join(', ')} } from ${JSON.stringify(runtimeModuleName)}\n`
|
||||||
|
)
|
||||||
|
push(
|
||||||
|
`\n// Binding optimization for webpack code-split\nconst ${ast.helpers
|
||||||
|
.map(s => `_${helperNameMap[s]} = ${helperNameMap[s]}`)
|
||||||
|
.join(', ')}\n`
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
push(
|
||||||
|
`import { ${ast.helpers
|
||||||
|
.map(s => `${helperNameMap[s]} as _${helperNameMap[s]}`)
|
||||||
|
.join(', ')} } from ${JSON.stringify(runtimeModuleName)}\n`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ast.ssrHelpers && ast.ssrHelpers.length) {
|
||||||
|
push(
|
||||||
|
`import { ${ast.ssrHelpers
|
||||||
|
.map(s => `${helperNameMap[s]} as _${helperNameMap[s]}`)
|
||||||
|
.join(', ')} } from "@vue/server-renderer"\n`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ast.imports.length) {
|
||||||
|
genImports(ast.imports, context)
|
||||||
|
newline()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (genScopeId) {
|
||||||
|
push(`const _withId = ${helper(WITH_SCOPE_ID)}("${scopeId}")`)
|
||||||
|
newline()
|
||||||
|
}
|
||||||
|
|
||||||
|
genHoists(ast.hoists, context)
|
||||||
|
newline()
|
||||||
|
push(`export `)
|
||||||
|
}
|
||||||
|
|
||||||
function genAssets(
|
function genAssets(
|
||||||
assets: string[],
|
assets: string[],
|
||||||
type: 'component' | 'directive',
|
type: 'component' | 'directive',
|
||||||
context: CodegenContext
|
{ helper, push, newline }: CodegenContext
|
||||||
) {
|
) {
|
||||||
const resolver = context.helper(
|
const resolver = helper(
|
||||||
type === 'component' ? RESOLVE_COMPONENT : RESOLVE_DIRECTIVE
|
type === 'component' ? RESOLVE_COMPONENT : RESOLVE_DIRECTIVE
|
||||||
)
|
)
|
||||||
for (let i = 0; i < assets.length; i++) {
|
for (let i = 0; i < assets.length; i++) {
|
||||||
const id = assets[i]
|
const id = assets[i]
|
||||||
context.push(
|
push(
|
||||||
`const ${toValidAssetId(id, type)} = ${resolver}(${JSON.stringify(id)})`
|
`const ${toValidAssetId(id, type)} = ${resolver}(${JSON.stringify(id)})`
|
||||||
)
|
)
|
||||||
context.newline()
|
if (i < assets.length - 1) {
|
||||||
|
newline()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -304,12 +433,27 @@ function genHoists(hoists: JSChildNode[], context: CodegenContext) {
|
|||||||
if (!hoists.length) {
|
if (!hoists.length) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
context.newline()
|
const { push, newline, helper, scopeId, mode } = context
|
||||||
|
const genScopeId = !__BROWSER__ && scopeId != null && mode !== 'function'
|
||||||
|
newline()
|
||||||
|
|
||||||
|
// push scope Id before initilaizing hoisted vnodes so that these vnodes
|
||||||
|
// get the proper scopeId as well.
|
||||||
|
if (genScopeId) {
|
||||||
|
push(`${helper(PUSH_SCOPE_ID)}("${scopeId}")`)
|
||||||
|
newline()
|
||||||
|
}
|
||||||
|
|
||||||
hoists.forEach((exp, i) => {
|
hoists.forEach((exp, i) => {
|
||||||
context.push(`const _hoisted_${i + 1} = `)
|
push(`const _hoisted_${i + 1} = `)
|
||||||
genNode(exp, context)
|
genNode(exp, context)
|
||||||
context.newline()
|
newline()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (genScopeId) {
|
||||||
|
push(`${helper(POP_SCOPE_ID)}()`)
|
||||||
|
newline()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function genImports(importsOptions: ImportItem[], context: CodegenContext) {
|
function genImports(importsOptions: ImportItem[], context: CodegenContext) {
|
||||||
@ -351,7 +495,8 @@ function genNodeListAsArray(
|
|||||||
function genNodeList(
|
function genNodeList(
|
||||||
nodes: (string | symbol | CodegenNode | TemplateChildNode[])[],
|
nodes: (string | symbol | CodegenNode | TemplateChildNode[])[],
|
||||||
context: CodegenContext,
|
context: CodegenContext,
|
||||||
multilines: boolean = false
|
multilines: boolean = false,
|
||||||
|
comma: boolean = true
|
||||||
) {
|
) {
|
||||||
const { push, newline } = context
|
const { push, newline } = context
|
||||||
for (let i = 0; i < nodes.length; i++) {
|
for (let i = 0; i < nodes.length; i++) {
|
||||||
@ -365,10 +510,10 @@ function genNodeList(
|
|||||||
}
|
}
|
||||||
if (i < nodes.length - 1) {
|
if (i < nodes.length - 1) {
|
||||||
if (multilines) {
|
if (multilines) {
|
||||||
push(',')
|
comma && push(',')
|
||||||
newline()
|
newline()
|
||||||
} else {
|
} else {
|
||||||
push(', ')
|
comma && push(', ')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -413,6 +558,10 @@ function genNode(node: CodegenNode | symbol | string, context: CodegenContext) {
|
|||||||
case NodeTypes.COMMENT:
|
case NodeTypes.COMMENT:
|
||||||
genComment(node, context)
|
genComment(node, context)
|
||||||
break
|
break
|
||||||
|
case NodeTypes.VNODE_CALL:
|
||||||
|
genVNodeCall(node, context)
|
||||||
|
break
|
||||||
|
|
||||||
case NodeTypes.JS_CALL_EXPRESSION:
|
case NodeTypes.JS_CALL_EXPRESSION:
|
||||||
genCallExpression(node, context)
|
genCallExpression(node, context)
|
||||||
break
|
break
|
||||||
@ -425,16 +574,37 @@ function genNode(node: CodegenNode | symbol | string, context: CodegenContext) {
|
|||||||
case NodeTypes.JS_FUNCTION_EXPRESSION:
|
case NodeTypes.JS_FUNCTION_EXPRESSION:
|
||||||
genFunctionExpression(node, context)
|
genFunctionExpression(node, context)
|
||||||
break
|
break
|
||||||
case NodeTypes.JS_SEQUENCE_EXPRESSION:
|
|
||||||
genSequenceExpression(node, context)
|
|
||||||
break
|
|
||||||
case NodeTypes.JS_CONDITIONAL_EXPRESSION:
|
case NodeTypes.JS_CONDITIONAL_EXPRESSION:
|
||||||
genConditionalExpression(node, context)
|
genConditionalExpression(node, context)
|
||||||
break
|
break
|
||||||
case NodeTypes.JS_CACHE_EXPRESSION:
|
case NodeTypes.JS_CACHE_EXPRESSION:
|
||||||
genCacheExpression(node, context)
|
genCacheExpression(node, context)
|
||||||
break
|
break
|
||||||
|
|
||||||
|
// SSR only types
|
||||||
|
case NodeTypes.JS_BLOCK_STATEMENT:
|
||||||
|
!__BROWSER__ && genNodeList(node.body, context, true, false)
|
||||||
|
break
|
||||||
|
case NodeTypes.JS_TEMPLATE_LITERAL:
|
||||||
|
!__BROWSER__ && genTemplateLiteral(node, context)
|
||||||
|
break
|
||||||
|
case NodeTypes.JS_IF_STATEMENT:
|
||||||
|
!__BROWSER__ && genIfStatement(node, context)
|
||||||
|
break
|
||||||
|
case NodeTypes.JS_ASSIGNMENT_EXPRESSION:
|
||||||
|
!__BROWSER__ && genAssignmentExpression(node, context)
|
||||||
|
break
|
||||||
|
case NodeTypes.JS_SEQUENCE_EXPRESSION:
|
||||||
|
!__BROWSER__ && genSequenceExpression(node, context)
|
||||||
|
break
|
||||||
|
case NodeTypes.JS_RETURN_STATEMENT:
|
||||||
|
!__BROWSER__ && genReturnStatement(node, context)
|
||||||
|
break
|
||||||
|
|
||||||
/* istanbul ignore next */
|
/* istanbul ignore next */
|
||||||
|
case NodeTypes.IF_BRANCH:
|
||||||
|
// noop
|
||||||
|
break
|
||||||
default:
|
default:
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
assert(false, `unhandled codegen node type: ${(node as any).type}`)
|
assert(false, `unhandled codegen node type: ${(node as any).type}`)
|
||||||
@ -459,7 +629,7 @@ function genExpression(node: SimpleExpressionNode, context: CodegenContext) {
|
|||||||
|
|
||||||
function genInterpolation(node: InterpolationNode, context: CodegenContext) {
|
function genInterpolation(node: InterpolationNode, context: CodegenContext) {
|
||||||
const { push, helper } = context
|
const { push, helper } = context
|
||||||
push(`${helper(TO_STRING)}(`)
|
push(`${helper(TO_DISPLAY_STRING)}(`)
|
||||||
genNode(node.content, context)
|
genNode(node.content, context)
|
||||||
push(`)`)
|
push(`)`)
|
||||||
}
|
}
|
||||||
@ -505,18 +675,60 @@ function genComment(node: CommentNode, context: CodegenContext) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function genVNodeCall(node: VNodeCall, context: CodegenContext) {
|
||||||
|
const { push, helper } = context
|
||||||
|
const {
|
||||||
|
tag,
|
||||||
|
props,
|
||||||
|
children,
|
||||||
|
patchFlag,
|
||||||
|
dynamicProps,
|
||||||
|
directives,
|
||||||
|
isBlock,
|
||||||
|
isForBlock
|
||||||
|
} = node
|
||||||
|
if (directives) {
|
||||||
|
push(helper(WITH_DIRECTIVES) + `(`)
|
||||||
|
}
|
||||||
|
if (isBlock) {
|
||||||
|
push(`(${helper(OPEN_BLOCK)}(${isForBlock ? `true` : ``}), `)
|
||||||
|
}
|
||||||
|
push(helper(isBlock ? CREATE_BLOCK : CREATE_VNODE) + `(`, node)
|
||||||
|
genNodeList(
|
||||||
|
genNullableArgs([tag, props, children, patchFlag, dynamicProps]),
|
||||||
|
context
|
||||||
|
)
|
||||||
|
push(`)`)
|
||||||
|
if (isBlock) {
|
||||||
|
push(`)`)
|
||||||
|
}
|
||||||
|
if (directives) {
|
||||||
|
push(`, `)
|
||||||
|
genNode(directives, context)
|
||||||
|
push(`)`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function genNullableArgs(args: any[]): CallExpression['arguments'] {
|
||||||
|
let i = args.length
|
||||||
|
while (i--) {
|
||||||
|
if (args[i] != null) break
|
||||||
|
}
|
||||||
|
return args.slice(0, i + 1).map(arg => arg || `null`)
|
||||||
|
}
|
||||||
|
|
||||||
// JavaScript
|
// JavaScript
|
||||||
function genCallExpression(node: CallExpression, context: CodegenContext) {
|
function genCallExpression(node: CallExpression, context: CodegenContext) {
|
||||||
const callee = isString(node.callee)
|
const callee = isString(node.callee)
|
||||||
? node.callee
|
? node.callee
|
||||||
: context.helper(node.callee)
|
: context.helper(node.callee)
|
||||||
context.push(callee + `(`, node, true)
|
context.push(callee + `(`, node)
|
||||||
genNodeList(node.arguments, context)
|
genNodeList(node.arguments, context)
|
||||||
context.push(`)`)
|
context.push(`)`)
|
||||||
}
|
}
|
||||||
|
|
||||||
function genObjectExpression(node: ObjectExpression, context: CodegenContext) {
|
function genObjectExpression(node: ObjectExpression, context: CodegenContext) {
|
||||||
const { push, indent, deindent, newline, resetMapping } = context
|
const { push, indent, deindent, newline } = context
|
||||||
const { properties } = node
|
const { properties } = node
|
||||||
if (!properties.length) {
|
if (!properties.length) {
|
||||||
push(`{}`, node)
|
push(`{}`, node)
|
||||||
@ -529,8 +741,7 @@ function genObjectExpression(node: ObjectExpression, context: CodegenContext) {
|
|||||||
push(multilines ? `{` : `{ `)
|
push(multilines ? `{` : `{ `)
|
||||||
multilines && indent()
|
multilines && indent()
|
||||||
for (let i = 0; i < properties.length; i++) {
|
for (let i = 0; i < properties.length; i++) {
|
||||||
const { key, value, loc } = properties[i]
|
const { key, value } = properties[i]
|
||||||
resetMapping(loc) // reset source mapping for every property.
|
|
||||||
// key
|
// key
|
||||||
genExpressionAsPropertyKey(key, context)
|
genExpressionAsPropertyKey(key, context)
|
||||||
push(`: `)
|
push(`: `)
|
||||||
@ -554,8 +765,17 @@ function genFunctionExpression(
|
|||||||
node: FunctionExpression,
|
node: FunctionExpression,
|
||||||
context: CodegenContext
|
context: CodegenContext
|
||||||
) {
|
) {
|
||||||
const { push, indent, deindent } = context
|
const { push, indent, deindent, scopeId, mode } = context
|
||||||
const { params, returns, newline } = node
|
const { params, returns, body, newline, isSlot } = node
|
||||||
|
// slot functions also need to push scopeId before rendering its content
|
||||||
|
const genScopeId =
|
||||||
|
!__BROWSER__ && isSlot && scopeId != null && mode !== 'function'
|
||||||
|
|
||||||
|
if (genScopeId) {
|
||||||
|
push(`_withId(`)
|
||||||
|
} else if (isSlot) {
|
||||||
|
push(`_${helperNameMap[WITH_CTX]}(`)
|
||||||
|
}
|
||||||
push(`(`, node)
|
push(`(`, node)
|
||||||
if (isArray(params)) {
|
if (isArray(params)) {
|
||||||
genNodeList(params, context)
|
genNodeList(params, context)
|
||||||
@ -563,27 +783,36 @@ function genFunctionExpression(
|
|||||||
genNode(params, context)
|
genNode(params, context)
|
||||||
}
|
}
|
||||||
push(`) => `)
|
push(`) => `)
|
||||||
if (newline) {
|
if (newline || body) {
|
||||||
push(`{`)
|
push(`{`)
|
||||||
indent()
|
indent()
|
||||||
push(`return `)
|
|
||||||
}
|
}
|
||||||
if (isArray(returns)) {
|
if (returns) {
|
||||||
genNodeListAsArray(returns, context)
|
if (newline) {
|
||||||
} else {
|
push(`return `)
|
||||||
genNode(returns, context)
|
}
|
||||||
|
if (isArray(returns)) {
|
||||||
|
genNodeListAsArray(returns, context)
|
||||||
|
} else {
|
||||||
|
genNode(returns, context)
|
||||||
|
}
|
||||||
|
} else if (body) {
|
||||||
|
genNode(body, context)
|
||||||
}
|
}
|
||||||
if (newline) {
|
if (newline || body) {
|
||||||
deindent()
|
deindent()
|
||||||
push(`}`)
|
push(`}`)
|
||||||
}
|
}
|
||||||
|
if (genScopeId || isSlot) {
|
||||||
|
push(`)`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function genConditionalExpression(
|
function genConditionalExpression(
|
||||||
node: ConditionalExpression,
|
node: ConditionalExpression,
|
||||||
context: CodegenContext
|
context: CodegenContext
|
||||||
) {
|
) {
|
||||||
const { test, consequent, alternate } = node
|
const { test, consequent, alternate, newline: needNewline } = node
|
||||||
const { push, indent, deindent, newline } = context
|
const { push, indent, deindent, newline } = context
|
||||||
if (test.type === NodeTypes.SIMPLE_EXPRESSION) {
|
if (test.type === NodeTypes.SIMPLE_EXPRESSION) {
|
||||||
const needsParens = !isSimpleIdentifier(test.content)
|
const needsParens = !isSimpleIdentifier(test.content)
|
||||||
@ -592,15 +821,17 @@ function genConditionalExpression(
|
|||||||
needsParens && push(`)`)
|
needsParens && push(`)`)
|
||||||
} else {
|
} else {
|
||||||
push(`(`)
|
push(`(`)
|
||||||
genCompoundExpression(test, context)
|
genNode(test, context)
|
||||||
push(`)`)
|
push(`)`)
|
||||||
}
|
}
|
||||||
indent()
|
needNewline && indent()
|
||||||
context.indentLevel++
|
context.indentLevel++
|
||||||
|
needNewline || push(` `)
|
||||||
push(`? `)
|
push(`? `)
|
||||||
genNode(consequent, context)
|
genNode(consequent, context)
|
||||||
context.indentLevel--
|
context.indentLevel--
|
||||||
newline()
|
needNewline && newline()
|
||||||
|
needNewline || push(` `)
|
||||||
push(`: `)
|
push(`: `)
|
||||||
const isNested = alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION
|
const isNested = alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION
|
||||||
if (!isNested) {
|
if (!isNested) {
|
||||||
@ -610,16 +841,7 @@ function genConditionalExpression(
|
|||||||
if (!isNested) {
|
if (!isNested) {
|
||||||
context.indentLevel--
|
context.indentLevel--
|
||||||
}
|
}
|
||||||
deindent(true /* without newline */)
|
needNewline && deindent(true /* without newline */)
|
||||||
}
|
|
||||||
|
|
||||||
function genSequenceExpression(
|
|
||||||
node: SequenceExpression,
|
|
||||||
context: CodegenContext
|
|
||||||
) {
|
|
||||||
context.push(`(`)
|
|
||||||
genNodeList(node.expressions, context)
|
|
||||||
context.push(`)`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function genCacheExpression(node: CacheExpression, context: CodegenContext) {
|
function genCacheExpression(node: CacheExpression, context: CodegenContext) {
|
||||||
@ -642,3 +864,77 @@ function genCacheExpression(node: CacheExpression, context: CodegenContext) {
|
|||||||
}
|
}
|
||||||
push(`)`)
|
push(`)`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function genTemplateLiteral(node: TemplateLiteral, context: CodegenContext) {
|
||||||
|
const { push, indent, deindent } = context
|
||||||
|
push('`')
|
||||||
|
const l = node.elements.length
|
||||||
|
const multilines = l > 3
|
||||||
|
for (let i = 0; i < l; i++) {
|
||||||
|
const e = node.elements[i]
|
||||||
|
if (isString(e)) {
|
||||||
|
push(e.replace(/`/g, '\\`'))
|
||||||
|
} else {
|
||||||
|
push('${')
|
||||||
|
if (multilines) indent()
|
||||||
|
genNode(e, context)
|
||||||
|
if (multilines) deindent()
|
||||||
|
push('}')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
push('`')
|
||||||
|
}
|
||||||
|
|
||||||
|
function genIfStatement(node: IfStatement, context: CodegenContext) {
|
||||||
|
const { push, indent, deindent } = context
|
||||||
|
const { test, consequent, alternate } = node
|
||||||
|
push(`if (`)
|
||||||
|
genNode(test, context)
|
||||||
|
push(`) {`)
|
||||||
|
indent()
|
||||||
|
genNode(consequent, context)
|
||||||
|
deindent()
|
||||||
|
push(`}`)
|
||||||
|
if (alternate) {
|
||||||
|
push(` else `)
|
||||||
|
if (alternate.type === NodeTypes.JS_IF_STATEMENT) {
|
||||||
|
genIfStatement(alternate, context)
|
||||||
|
} else {
|
||||||
|
push(`{`)
|
||||||
|
indent()
|
||||||
|
genNode(alternate, context)
|
||||||
|
deindent()
|
||||||
|
push(`}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function genAssignmentExpression(
|
||||||
|
node: AssignmentExpression,
|
||||||
|
context: CodegenContext
|
||||||
|
) {
|
||||||
|
genNode(node.left, context)
|
||||||
|
context.push(` = `)
|
||||||
|
genNode(node.right, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
function genSequenceExpression(
|
||||||
|
node: SequenceExpression,
|
||||||
|
context: CodegenContext
|
||||||
|
) {
|
||||||
|
context.push(`(`)
|
||||||
|
genNodeList(node.expressions, context)
|
||||||
|
context.push(`)`)
|
||||||
|
}
|
||||||
|
|
||||||
|
function genReturnStatement(
|
||||||
|
{ returns }: ReturnStatement,
|
||||||
|
context: CodegenContext
|
||||||
|
) {
|
||||||
|
context.push(`return `)
|
||||||
|
if (isArray(returns)) {
|
||||||
|
genNodeListAsArray(returns, context)
|
||||||
|
} else {
|
||||||
|
genNode(returns, context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
100
packages/compiler-core/src/compile.ts
Normal file
100
packages/compiler-core/src/compile.ts
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
import { CompilerOptions } from './options'
|
||||||
|
import { baseParse } from './parse'
|
||||||
|
import { transform, NodeTransform, DirectiveTransform } from './transform'
|
||||||
|
import { generate, CodegenResult } from './codegen'
|
||||||
|
import { RootNode } from './ast'
|
||||||
|
import { isString } from '@vue/shared'
|
||||||
|
import { transformIf } from './transforms/vIf'
|
||||||
|
import { transformFor } from './transforms/vFor'
|
||||||
|
import { transformExpression } from './transforms/transformExpression'
|
||||||
|
import { transformSlotOutlet } from './transforms/transformSlotOutlet'
|
||||||
|
import { transformElement } from './transforms/transformElement'
|
||||||
|
import { transformOn } from './transforms/vOn'
|
||||||
|
import { transformBind } from './transforms/vBind'
|
||||||
|
import { trackSlotScopes, trackVForSlotScopes } from './transforms/vSlot'
|
||||||
|
import { transformText } from './transforms/transformText'
|
||||||
|
import { transformOnce } from './transforms/vOnce'
|
||||||
|
import { transformModel } from './transforms/vModel'
|
||||||
|
import { defaultOnError, createCompilerError, ErrorCodes } from './errors'
|
||||||
|
|
||||||
|
export type TransformPreset = [
|
||||||
|
NodeTransform[],
|
||||||
|
Record<string, DirectiveTransform>
|
||||||
|
]
|
||||||
|
|
||||||
|
export function getBaseTransformPreset(
|
||||||
|
prefixIdentifiers?: boolean
|
||||||
|
): TransformPreset {
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
transformOnce,
|
||||||
|
transformIf,
|
||||||
|
transformFor,
|
||||||
|
...(!__BROWSER__ && prefixIdentifiers
|
||||||
|
? [
|
||||||
|
// order is important
|
||||||
|
trackVForSlotScopes,
|
||||||
|
transformExpression
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
transformSlotOutlet,
|
||||||
|
transformElement,
|
||||||
|
trackSlotScopes,
|
||||||
|
transformText
|
||||||
|
],
|
||||||
|
{
|
||||||
|
on: transformOn,
|
||||||
|
bind: transformBind,
|
||||||
|
model: transformModel
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
// we name it `baseCompile` so that higher order compilers like
|
||||||
|
// @vue/compiler-dom can export `compile` while re-exporting everything else.
|
||||||
|
export function baseCompile(
|
||||||
|
template: string | RootNode,
|
||||||
|
options: CompilerOptions = {}
|
||||||
|
): CodegenResult {
|
||||||
|
const onError = options.onError || defaultOnError
|
||||||
|
const isModuleMode = options.mode === 'module'
|
||||||
|
/* istanbul ignore if */
|
||||||
|
if (__BROWSER__) {
|
||||||
|
if (options.prefixIdentifiers === true) {
|
||||||
|
onError(createCompilerError(ErrorCodes.X_PREFIX_ID_NOT_SUPPORTED))
|
||||||
|
} else if (isModuleMode) {
|
||||||
|
onError(createCompilerError(ErrorCodes.X_MODULE_MODE_NOT_SUPPORTED))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const prefixIdentifiers =
|
||||||
|
!__BROWSER__ && (options.prefixIdentifiers === true || isModuleMode)
|
||||||
|
if (!prefixIdentifiers && options.cacheHandlers) {
|
||||||
|
onError(createCompilerError(ErrorCodes.X_CACHE_HANDLER_NOT_SUPPORTED))
|
||||||
|
}
|
||||||
|
if (options.scopeId && !isModuleMode) {
|
||||||
|
onError(createCompilerError(ErrorCodes.X_SCOPE_ID_NOT_SUPPORTED))
|
||||||
|
}
|
||||||
|
|
||||||
|
const ast = isString(template) ? baseParse(template, options) : template
|
||||||
|
const [nodeTransforms, directiveTransforms] = getBaseTransformPreset(
|
||||||
|
prefixIdentifiers
|
||||||
|
)
|
||||||
|
transform(ast, {
|
||||||
|
...options,
|
||||||
|
prefixIdentifiers,
|
||||||
|
nodeTransforms: [
|
||||||
|
...nodeTransforms,
|
||||||
|
...(options.nodeTransforms || []) // user transforms
|
||||||
|
],
|
||||||
|
directiveTransforms: {
|
||||||
|
...directiveTransforms,
|
||||||
|
...(options.directiveTransforms || {}) // user transforms
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return generate(ast, {
|
||||||
|
...options,
|
||||||
|
prefixIdentifiers
|
||||||
|
})
|
||||||
|
}
|
@ -16,11 +16,14 @@ export function defaultOnError(error: CompilerError) {
|
|||||||
export function createCompilerError<T extends number>(
|
export function createCompilerError<T extends number>(
|
||||||
code: T,
|
code: T,
|
||||||
loc?: SourceLocation,
|
loc?: SourceLocation,
|
||||||
messages?: { [code: number]: string }
|
messages?: { [code: number]: string },
|
||||||
|
additionalMessage?: string
|
||||||
): T extends ErrorCodes ? CoreCompilerError : CompilerError {
|
): T extends ErrorCodes ? CoreCompilerError : CompilerError {
|
||||||
const msg = __DEV__ || !__BROWSER__ ? (messages || errorMessages)[code] : code
|
const msg =
|
||||||
const locInfo = loc ? ` (${loc.start.line}:${loc.start.column})` : ``
|
__DEV__ || !__BROWSER__
|
||||||
const error = new SyntaxError(msg + locInfo) as CompilerError
|
? (messages || errorMessages)[code] + (additionalMessage || ``)
|
||||||
|
: code
|
||||||
|
const error = new SyntaxError(String(msg)) as CompilerError
|
||||||
error.code = code
|
error.code = code
|
||||||
error.loc = loc
|
error.loc = loc
|
||||||
return error as any
|
return error as any
|
||||||
@ -58,7 +61,6 @@ export const enum ErrorCodes {
|
|||||||
UNEXPECTED_NULL_CHARACTER,
|
UNEXPECTED_NULL_CHARACTER,
|
||||||
UNEXPECTED_QUESTION_MARK_INSTEAD_OF_TAG_NAME,
|
UNEXPECTED_QUESTION_MARK_INSTEAD_OF_TAG_NAME,
|
||||||
UNEXPECTED_SOLIDUS_IN_TAG,
|
UNEXPECTED_SOLIDUS_IN_TAG,
|
||||||
UNKNOWN_NAMED_CHARACTER_REFERENCE,
|
|
||||||
|
|
||||||
// Vue-specific parse errors
|
// Vue-specific parse errors
|
||||||
X_INVALID_END_TAG,
|
X_INVALID_END_TAG,
|
||||||
@ -74,19 +76,21 @@ export const enum ErrorCodes {
|
|||||||
X_V_BIND_NO_EXPRESSION,
|
X_V_BIND_NO_EXPRESSION,
|
||||||
X_V_ON_NO_EXPRESSION,
|
X_V_ON_NO_EXPRESSION,
|
||||||
X_V_SLOT_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET,
|
X_V_SLOT_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET,
|
||||||
X_V_SLOT_NAMED_SLOT_ON_COMPONENT,
|
|
||||||
X_V_SLOT_MIXED_SLOT_USAGE,
|
X_V_SLOT_MIXED_SLOT_USAGE,
|
||||||
X_V_SLOT_DUPLICATE_SLOT_NAMES,
|
X_V_SLOT_DUPLICATE_SLOT_NAMES,
|
||||||
X_V_SLOT_EXTRANEOUS_NON_SLOT_CHILDREN,
|
X_V_SLOT_EXTRANEOUS_DEFAULT_SLOT_CHILDREN,
|
||||||
X_V_SLOT_MISPLACED,
|
X_V_SLOT_MISPLACED,
|
||||||
X_V_MODEL_NO_EXPRESSION,
|
X_V_MODEL_NO_EXPRESSION,
|
||||||
X_V_MODEL_MALFORMED_EXPRESSION,
|
X_V_MODEL_MALFORMED_EXPRESSION,
|
||||||
X_V_MODEL_ON_SCOPE_VARIABLE,
|
X_V_MODEL_ON_SCOPE_VARIABLE,
|
||||||
X_INVALID_EXPRESSION,
|
X_INVALID_EXPRESSION,
|
||||||
|
X_KEEP_ALIVE_INVALID_CHILDREN,
|
||||||
|
|
||||||
// generic errors
|
// generic errors
|
||||||
X_PREFIX_ID_NOT_SUPPORTED,
|
X_PREFIX_ID_NOT_SUPPORTED,
|
||||||
X_MODULE_MODE_NOT_SUPPORTED,
|
X_MODULE_MODE_NOT_SUPPORTED,
|
||||||
|
X_CACHE_HANDLER_NOT_SUPPORTED,
|
||||||
|
X_SCOPE_ID_NOT_SUPPORTED,
|
||||||
|
|
||||||
// Special value for higher-order compilers to pick up the last code
|
// Special value for higher-order compilers to pick up the last code
|
||||||
// to avoid collision of error codes. This should always be kept as the last
|
// to avoid collision of error codes. This should always be kept as the last
|
||||||
@ -140,11 +144,10 @@ export const errorMessages: { [code: number]: string } = {
|
|||||||
[ErrorCodes.UNEXPECTED_QUESTION_MARK_INSTEAD_OF_TAG_NAME]:
|
[ErrorCodes.UNEXPECTED_QUESTION_MARK_INSTEAD_OF_TAG_NAME]:
|
||||||
"'<?' is allowed only in XML context.",
|
"'<?' is allowed only in XML context.",
|
||||||
[ErrorCodes.UNEXPECTED_SOLIDUS_IN_TAG]: "Illegal '/' in tags.",
|
[ErrorCodes.UNEXPECTED_SOLIDUS_IN_TAG]: "Illegal '/' in tags.",
|
||||||
[ErrorCodes.UNKNOWN_NAMED_CHARACTER_REFERENCE]: 'Unknown entity name.',
|
|
||||||
|
|
||||||
// Vue-specific parse errors
|
// Vue-specific parse errors
|
||||||
[ErrorCodes.X_INVALID_END_TAG]: 'Invalid end tag.',
|
[ErrorCodes.X_INVALID_END_TAG]: 'Invalid end tag.',
|
||||||
[ErrorCodes.X_MISSING_END_TAG]: 'End tag was not found.',
|
[ErrorCodes.X_MISSING_END_TAG]: 'Element is missing end tag.',
|
||||||
[ErrorCodes.X_MISSING_INTERPOLATION_END]:
|
[ErrorCodes.X_MISSING_INTERPOLATION_END]:
|
||||||
'Interpolation end sign was not found.',
|
'Interpolation end sign was not found.',
|
||||||
[ErrorCodes.X_MISSING_DYNAMIC_DIRECTIVE_ARGUMENT_END]:
|
[ErrorCodes.X_MISSING_DYNAMIC_DIRECTIVE_ARGUMENT_END]:
|
||||||
@ -159,24 +162,24 @@ export const errorMessages: { [code: number]: string } = {
|
|||||||
[ErrorCodes.X_V_BIND_NO_EXPRESSION]: `v-bind is missing expression.`,
|
[ErrorCodes.X_V_BIND_NO_EXPRESSION]: `v-bind is missing expression.`,
|
||||||
[ErrorCodes.X_V_ON_NO_EXPRESSION]: `v-on is missing expression.`,
|
[ErrorCodes.X_V_ON_NO_EXPRESSION]: `v-on is missing expression.`,
|
||||||
[ErrorCodes.X_V_SLOT_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET]: `Unexpected custom directive on <slot> outlet.`,
|
[ErrorCodes.X_V_SLOT_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET]: `Unexpected custom directive on <slot> outlet.`,
|
||||||
[ErrorCodes.X_V_SLOT_NAMED_SLOT_ON_COMPONENT]:
|
|
||||||
`Named v-slot on component. ` +
|
|
||||||
`Named slots should use <template v-slot> syntax nested inside the component.`,
|
|
||||||
[ErrorCodes.X_V_SLOT_MIXED_SLOT_USAGE]:
|
[ErrorCodes.X_V_SLOT_MIXED_SLOT_USAGE]:
|
||||||
`Mixed v-slot usage on both the component and nested <template>.` +
|
`Mixed v-slot usage on both the component and nested <template>.` +
|
||||||
`The default slot should also use <template> syntax when there are other ` +
|
`When there are multiple named slots, all slots should use <template> ` +
|
||||||
`named slots to avoid scope ambiguity.`,
|
`syntax to avoid scope ambiguity.`,
|
||||||
[ErrorCodes.X_V_SLOT_DUPLICATE_SLOT_NAMES]: `Duplicate slot names found. `,
|
[ErrorCodes.X_V_SLOT_DUPLICATE_SLOT_NAMES]: `Duplicate slot names found. `,
|
||||||
[ErrorCodes.X_V_SLOT_EXTRANEOUS_NON_SLOT_CHILDREN]:
|
[ErrorCodes.X_V_SLOT_EXTRANEOUS_DEFAULT_SLOT_CHILDREN]:
|
||||||
`Extraneous children found when component has explicit slots. ` +
|
`Extraneous children found when component already has explicitly named ` +
|
||||||
`These children will be ignored.`,
|
`default slot. These children will be ignored.`,
|
||||||
[ErrorCodes.X_V_SLOT_MISPLACED]: `v-slot can only be used on components or <template> tags.`,
|
[ErrorCodes.X_V_SLOT_MISPLACED]: `v-slot can only be used on components or <template> tags.`,
|
||||||
[ErrorCodes.X_V_MODEL_NO_EXPRESSION]: `v-model is missing expression.`,
|
[ErrorCodes.X_V_MODEL_NO_EXPRESSION]: `v-model is missing expression.`,
|
||||||
[ErrorCodes.X_V_MODEL_MALFORMED_EXPRESSION]: `v-model value must be a valid JavaScript member expression.`,
|
[ErrorCodes.X_V_MODEL_MALFORMED_EXPRESSION]: `v-model value must be a valid JavaScript member expression.`,
|
||||||
[ErrorCodes.X_V_MODEL_ON_SCOPE_VARIABLE]: `v-model cannot be used on v-for or v-slot scope variables because they are not writable.`,
|
[ErrorCodes.X_V_MODEL_ON_SCOPE_VARIABLE]: `v-model cannot be used on v-for or v-slot scope variables because they are not writable.`,
|
||||||
[ErrorCodes.X_INVALID_EXPRESSION]: `Invalid JavaScript expression.`,
|
[ErrorCodes.X_INVALID_EXPRESSION]: `Error parsing JavaScript expression: `,
|
||||||
|
[ErrorCodes.X_KEEP_ALIVE_INVALID_CHILDREN]: `<KeepAlive> expects exactly one child component.`,
|
||||||
|
|
||||||
// generic errors
|
// generic errors
|
||||||
[ErrorCodes.X_PREFIX_ID_NOT_SUPPORTED]: `"prefixIdentifiers" option is not supported in this build of compiler.`,
|
[ErrorCodes.X_PREFIX_ID_NOT_SUPPORTED]: `"prefixIdentifiers" option is not supported in this build of compiler.`,
|
||||||
[ErrorCodes.X_MODULE_MODE_NOT_SUPPORTED]: `ES module mode is not supported in this build of compiler.`
|
[ErrorCodes.X_MODULE_MODE_NOT_SUPPORTED]: `ES module mode is not supported in this build of compiler.`,
|
||||||
|
[ErrorCodes.X_CACHE_HANDLER_NOT_SUPPORTED]: `"cacheHandlers" option is only supported when the "prefixIdentifiers" option is enabled.`,
|
||||||
|
[ErrorCodes.X_SCOPE_ID_NOT_SUPPORTED]: `"scopeId" option is only supported in module mode.`
|
||||||
}
|
}
|
||||||
|
@ -1,90 +1,20 @@
|
|||||||
import { CompilerOptions } from './options'
|
export { baseCompile } from './compile'
|
||||||
import { parse } from './parse'
|
|
||||||
import { transform } from './transform'
|
|
||||||
import { generate, CodegenResult } from './codegen'
|
|
||||||
import { RootNode } from './ast'
|
|
||||||
import { isString } from '@vue/shared'
|
|
||||||
import { transformIf } from './transforms/vIf'
|
|
||||||
import { transformFor } from './transforms/vFor'
|
|
||||||
import { transformExpression } from './transforms/transformExpression'
|
|
||||||
import { transformSlotOutlet } from './transforms/transformSlotOutlet'
|
|
||||||
import { transformElement } from './transforms/transformElement'
|
|
||||||
import { transformOn } from './transforms/vOn'
|
|
||||||
import { transformBind } from './transforms/vBind'
|
|
||||||
import { defaultOnError, createCompilerError, ErrorCodes } from './errors'
|
|
||||||
import { trackSlotScopes, trackVForSlotScopes } from './transforms/vSlot'
|
|
||||||
import { transformText } from './transforms/transformText'
|
|
||||||
import { transformOnce } from './transforms/vOnce'
|
|
||||||
import { transformModel } from './transforms/vModel'
|
|
||||||
|
|
||||||
// we name it `baseCompile` so that higher order compilers like @vue/compiler-dom
|
|
||||||
// can export `compile` while re-exporting everything else.
|
|
||||||
export function baseCompile(
|
|
||||||
template: string | RootNode,
|
|
||||||
options: CompilerOptions = {}
|
|
||||||
): CodegenResult {
|
|
||||||
/* istanbul ignore if */
|
|
||||||
if (__BROWSER__) {
|
|
||||||
const onError = options.onError || defaultOnError
|
|
||||||
if (options.prefixIdentifiers === true) {
|
|
||||||
onError(createCompilerError(ErrorCodes.X_PREFIX_ID_NOT_SUPPORTED))
|
|
||||||
} else if (options.mode === 'module') {
|
|
||||||
onError(createCompilerError(ErrorCodes.X_MODULE_MODE_NOT_SUPPORTED))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const ast = isString(template) ? parse(template, options) : template
|
|
||||||
|
|
||||||
const prefixIdentifiers =
|
|
||||||
!__BROWSER__ &&
|
|
||||||
(options.prefixIdentifiers === true || options.mode === 'module')
|
|
||||||
|
|
||||||
transform(ast, {
|
|
||||||
...options,
|
|
||||||
prefixIdentifiers,
|
|
||||||
nodeTransforms: [
|
|
||||||
transformOnce,
|
|
||||||
transformIf,
|
|
||||||
transformFor,
|
|
||||||
...(prefixIdentifiers
|
|
||||||
? [
|
|
||||||
// order is important
|
|
||||||
trackVForSlotScopes,
|
|
||||||
transformExpression
|
|
||||||
]
|
|
||||||
: []),
|
|
||||||
transformSlotOutlet,
|
|
||||||
transformElement,
|
|
||||||
trackSlotScopes,
|
|
||||||
transformText,
|
|
||||||
...(options.nodeTransforms || []) // user transforms
|
|
||||||
],
|
|
||||||
directiveTransforms: {
|
|
||||||
on: transformOn,
|
|
||||||
bind: transformBind,
|
|
||||||
model: transformModel,
|
|
||||||
...(options.directiveTransforms || {}) // user transforms
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return generate(ast, {
|
|
||||||
...options,
|
|
||||||
prefixIdentifiers
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Also expose lower level APIs & types
|
// Also expose lower level APIs & types
|
||||||
export {
|
export {
|
||||||
CompilerOptions,
|
CompilerOptions,
|
||||||
ParserOptions,
|
ParserOptions,
|
||||||
TransformOptions,
|
TransformOptions,
|
||||||
CodegenOptions
|
CodegenOptions,
|
||||||
|
HoistTransform
|
||||||
} from './options'
|
} from './options'
|
||||||
export { parse, TextModes } from './parse'
|
export { baseParse, TextModes } from './parse'
|
||||||
export {
|
export {
|
||||||
transform,
|
transform,
|
||||||
createStructuralDirectiveTransform,
|
|
||||||
TransformContext,
|
TransformContext,
|
||||||
|
createTransformContext,
|
||||||
|
traverseNode,
|
||||||
|
createStructuralDirectiveTransform,
|
||||||
NodeTransform,
|
NodeTransform,
|
||||||
StructuralDirectiveTransform,
|
StructuralDirectiveTransform,
|
||||||
DirectiveTransform
|
DirectiveTransform
|
||||||
@ -96,19 +26,32 @@ export {
|
|||||||
CompilerError,
|
CompilerError,
|
||||||
createCompilerError
|
createCompilerError
|
||||||
} from './errors'
|
} from './errors'
|
||||||
|
|
||||||
export * from './ast'
|
export * from './ast'
|
||||||
export * from './utils'
|
export * from './utils'
|
||||||
export { registerRuntimeHelpers } from './runtimeHelpers'
|
export * from './runtimeHelpers'
|
||||||
|
|
||||||
// expose transforms so higher-order compilers can import and extend them
|
export { getBaseTransformPreset, TransformPreset } from './compile'
|
||||||
export { transformModel } from './transforms/vModel'
|
export { transformModel } from './transforms/vModel'
|
||||||
export { transformOn } from './transforms/vOn'
|
export { transformOn } from './transforms/vOn'
|
||||||
|
export { transformBind } from './transforms/vBind'
|
||||||
// utility, but need to rewrite typing to avoid dts relying on @vue/shared
|
export { noopDirectiveTransform } from './transforms/noopDirectiveTransform'
|
||||||
import { generateCodeFrame as _genCodeFrame } from '@vue/shared'
|
export { processIf } from './transforms/vIf'
|
||||||
const generateCodeFrame = _genCodeFrame as (
|
export { processFor, createForLoopParams } from './transforms/vFor'
|
||||||
source: string,
|
export {
|
||||||
start?: number,
|
transformExpression,
|
||||||
end?: number
|
processExpression
|
||||||
) => string
|
} from './transforms/transformExpression'
|
||||||
export { generateCodeFrame }
|
export {
|
||||||
|
buildSlots,
|
||||||
|
SlotFnBuilder,
|
||||||
|
trackVForSlotScopes,
|
||||||
|
trackSlotScopes
|
||||||
|
} from './transforms/vSlot'
|
||||||
|
export {
|
||||||
|
transformElement,
|
||||||
|
resolveComponentType,
|
||||||
|
buildProps
|
||||||
|
} from './transforms/transformElement'
|
||||||
|
export { processSlotOutlet } from './transforms/transformSlotOutlet'
|
||||||
|
export { generateCodeFrame } from '@vue/shared'
|
||||||
|
@ -1,16 +1,30 @@
|
|||||||
import { ElementNode, Namespace } from './ast'
|
import { ElementNode, Namespace, JSChildNode, PlainElementNode } from './ast'
|
||||||
import { TextModes } from './parse'
|
import { TextModes } from './parse'
|
||||||
import { CompilerError } from './errors'
|
import { CompilerError } from './errors'
|
||||||
import { NodeTransform, DirectiveTransform } from './transform'
|
import {
|
||||||
|
NodeTransform,
|
||||||
|
DirectiveTransform,
|
||||||
|
TransformContext
|
||||||
|
} from './transform'
|
||||||
|
import { ParserPlugin } from '@babel/parser'
|
||||||
|
|
||||||
export interface ParserOptions {
|
export interface ParserOptions {
|
||||||
isVoidTag?: (tag: string) => boolean // e.g. img, br, hr
|
// e.g. platform native elements, e.g. <div> for browsers
|
||||||
isNativeTag?: (tag: string) => boolean // e.g. loading-indicator in weex
|
isNativeTag?: (tag: string) => boolean
|
||||||
isPreTag?: (tag: string) => boolean // e.g. <pre> where whitespace is intact
|
// e.g. native elements that can self-close, e.g. <img>, <br>, <hr>
|
||||||
isCustomElement?: (tag: string) => boolean
|
isVoidTag?: (tag: string) => boolean
|
||||||
|
// e.g. elements that should preserve whitespace inside, e.g. <pre>
|
||||||
|
isPreTag?: (tag: string) => boolean
|
||||||
|
// platform-specific built-in components e.g. <Transition>
|
||||||
isBuiltInComponent?: (tag: string) => symbol | void
|
isBuiltInComponent?: (tag: string) => symbol | void
|
||||||
|
// separate option for end users to extend the native elements list
|
||||||
|
isCustomElement?: (tag: string) => boolean
|
||||||
getNamespace?: (tag: string, parent: ElementNode | undefined) => Namespace
|
getNamespace?: (tag: string, parent: ElementNode | undefined) => Namespace
|
||||||
getTextMode?: (tag: string, ns: Namespace) => TextModes
|
getTextMode?: (
|
||||||
|
tag: string,
|
||||||
|
ns: Namespace,
|
||||||
|
parent: ElementNode | undefined
|
||||||
|
) => TextModes
|
||||||
delimiters?: [string, string] // ['{{', '}}']
|
delimiters?: [string, string] // ['{{', '}}']
|
||||||
|
|
||||||
// Map to HTML entities. E.g., `{ "amp;": "&" }`
|
// Map to HTML entities. E.g., `{ "amp;": "&" }`
|
||||||
@ -19,27 +33,46 @@ export interface ParserOptions {
|
|||||||
// this number is based on the map above, but it should be pre-computed
|
// this number is based on the map above, but it should be pre-computed
|
||||||
// to avoid the cost on every parse() call.
|
// to avoid the cost on every parse() call.
|
||||||
maxCRNameLength?: number
|
maxCRNameLength?: number
|
||||||
|
|
||||||
onError?: (error: CompilerError) => void
|
onError?: (error: CompilerError) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type HoistTransform = (
|
||||||
|
node: PlainElementNode,
|
||||||
|
context: TransformContext
|
||||||
|
) => JSChildNode
|
||||||
|
|
||||||
export interface TransformOptions {
|
export interface TransformOptions {
|
||||||
nodeTransforms?: NodeTransform[]
|
nodeTransforms?: NodeTransform[]
|
||||||
directiveTransforms?: { [name: string]: DirectiveTransform }
|
directiveTransforms?: Record<string, DirectiveTransform | undefined>
|
||||||
|
// an optional hook to transform a node being hoisted.
|
||||||
|
// used by compiler-dom to turn hoisted nodes into stringified HTML vnodes.
|
||||||
|
transformHoist?: HoistTransform | null
|
||||||
isBuiltInComponent?: (tag: string) => symbol | void
|
isBuiltInComponent?: (tag: string) => symbol | void
|
||||||
// Transform expressions like {{ foo }} to `_ctx.foo`.
|
// Transform expressions like {{ foo }} to `_ctx.foo`.
|
||||||
// Default: mode === 'module'
|
// If this option is false, the generated code will be wrapped in a
|
||||||
|
// `with (this) { ... }` block.
|
||||||
|
// - This is force-enabled in module mode, since modules are by default strict
|
||||||
|
// and cannot use `with`
|
||||||
|
// - Default: mode === 'module'
|
||||||
prefixIdentifiers?: boolean
|
prefixIdentifiers?: boolean
|
||||||
// Hoist static VNodes and props objects to `_hoisted_x` constants
|
// Hoist static VNodes and props objects to `_hoisted_x` constants
|
||||||
// Default: false
|
// - Default: false
|
||||||
hoistStatic?: boolean
|
hoistStatic?: boolean
|
||||||
// Cache v-on handlers to avoid creating new inline functions on each render,
|
// Cache v-on handlers to avoid creating new inline functions on each render,
|
||||||
// also avoids the need for dynamically patching the handlers by wrapping it.
|
// also avoids the need for dynamically patching the handlers by wrapping it.
|
||||||
// e.g `@click="foo"` by default is compiled to `{ onClick: foo }`. With this
|
// e.g `@click="foo"` by default is compiled to `{ onClick: foo }`. With this
|
||||||
// option it's compiled to:
|
// option it's compiled to:
|
||||||
// `{ onClick: _cache[0] || (_cache[0] = e => _ctx.foo(e)) }`
|
// `{ onClick: _cache[0] || (_cache[0] = e => _ctx.foo(e)) }`
|
||||||
// Default: false
|
// - Requires "prefixIdentifiers" to be enabled because it relies on scope
|
||||||
|
// analysis to determine if a handler is safe to cache.
|
||||||
|
// - Default: false
|
||||||
cacheHandlers?: boolean
|
cacheHandlers?: boolean
|
||||||
|
// a list of parser plugins to enable for @babel/parser
|
||||||
|
// https://babeljs.io/docs/en/next/babel-parser#plugins
|
||||||
|
expressionPlugins?: ParserPlugin[]
|
||||||
|
// SFC scoped styles ID
|
||||||
|
scopeId?: string | null
|
||||||
|
ssr?: boolean
|
||||||
onError?: (error: CompilerError) => void
|
onError?: (error: CompilerError) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,19 +82,26 @@ export interface CodegenOptions {
|
|||||||
// - Function mode will generate a single `const { helpers... } = Vue`
|
// - Function mode will generate a single `const { helpers... } = Vue`
|
||||||
// statement and return the render function. It is meant to be used with
|
// statement and return the render function. It is meant to be used with
|
||||||
// `new Function(code)()` to generate a render function at runtime.
|
// `new Function(code)()` to generate a render function at runtime.
|
||||||
// Default: 'function'
|
// - Default: 'function'
|
||||||
mode?: 'module' | 'function'
|
mode?: 'module' | 'function'
|
||||||
// Prefix suitable identifiers with _ctx.
|
|
||||||
// If this option is false, the generated code will be wrapped in a
|
|
||||||
// `with (this) { ... }` block.
|
|
||||||
// Default: false
|
|
||||||
prefixIdentifiers?: boolean
|
|
||||||
// Generate source map?
|
// Generate source map?
|
||||||
// Default: false
|
// - Default: false
|
||||||
sourceMap?: boolean
|
sourceMap?: boolean
|
||||||
// Filename for source map generation.
|
// Filename for source map generation.
|
||||||
// Default: `template.vue.html`
|
// - Default: `template.vue.html`
|
||||||
filename?: string
|
filename?: string
|
||||||
|
// SFC scoped styles ID
|
||||||
|
scopeId?: string | null
|
||||||
|
// we need to know about this to generate proper preambles
|
||||||
|
prefixIdentifiers?: boolean
|
||||||
|
// option to optimize helper import bindings via variable assignment
|
||||||
|
// (only used for webpack code-split)
|
||||||
|
optimizeBindings?: boolean
|
||||||
|
// for specifying where to import helpers
|
||||||
|
runtimeModuleName?: string
|
||||||
|
runtimeGlobalName?: string
|
||||||
|
// generate ssr-specific code?
|
||||||
|
ssr?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CompilerOptions = ParserOptions & TransformOptions & CodegenOptions
|
export type CompilerOptions = ParserOptions & TransformOptions & CodegenOptions
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { ParserOptions } from './options'
|
import { ParserOptions } from './options'
|
||||||
import { NO, isArray } from '@vue/shared'
|
import { NO, isArray, makeMap } from '@vue/shared'
|
||||||
import { ErrorCodes, createCompilerError, defaultOnError } from './errors'
|
import { ErrorCodes, createCompilerError, defaultOnError } from './errors'
|
||||||
import {
|
import {
|
||||||
assert,
|
assert,
|
||||||
@ -21,11 +21,11 @@ import {
|
|||||||
SourceLocation,
|
SourceLocation,
|
||||||
TextNode,
|
TextNode,
|
||||||
TemplateChildNode,
|
TemplateChildNode,
|
||||||
InterpolationNode
|
InterpolationNode,
|
||||||
|
createRoot
|
||||||
} from './ast'
|
} from './ast'
|
||||||
import { extend } from '@vue/shared'
|
import { extend } from '@vue/shared'
|
||||||
|
|
||||||
// `isNativeTag` is optional, others are required
|
|
||||||
type OptionalOptions = 'isNativeTag' | 'isBuiltInComponent'
|
type OptionalOptions = 'isNativeTag' | 'isBuiltInComponent'
|
||||||
type MergedParserOptions = Omit<Required<ParserOptions>, OptionalOptions> &
|
type MergedParserOptions = Omit<Required<ParserOptions>, OptionalOptions> &
|
||||||
Pick<ParserOptions, OptionalOptions>
|
Pick<ParserOptions, OptionalOptions>
|
||||||
@ -64,25 +64,20 @@ interface ParserContext {
|
|||||||
offset: number
|
offset: number
|
||||||
line: number
|
line: number
|
||||||
column: number
|
column: number
|
||||||
inPre: boolean
|
inPre: boolean // HTML <pre> tag, preserve whitespaces
|
||||||
|
inVPre: boolean // v-pre, do not process directives and interpolations
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parse(content: string, options: ParserOptions = {}): RootNode {
|
export function baseParse(
|
||||||
|
content: string,
|
||||||
|
options: ParserOptions = {}
|
||||||
|
): RootNode {
|
||||||
const context = createParserContext(content, options)
|
const context = createParserContext(content, options)
|
||||||
const start = getCursor(context)
|
const start = getCursor(context)
|
||||||
|
return createRoot(
|
||||||
return {
|
parseChildren(context, TextModes.DATA, []),
|
||||||
type: NodeTypes.ROOT,
|
getSelection(context, start)
|
||||||
children: parseChildren(context, TextModes.DATA, []),
|
)
|
||||||
helpers: [],
|
|
||||||
components: [],
|
|
||||||
directives: [],
|
|
||||||
hoists: [],
|
|
||||||
imports: [],
|
|
||||||
cached: 0,
|
|
||||||
codegenNode: undefined,
|
|
||||||
loc: getSelection(context, start)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function createParserContext(
|
function createParserContext(
|
||||||
@ -99,7 +94,8 @@ function createParserContext(
|
|||||||
offset: 0,
|
offset: 0,
|
||||||
originalSource: content,
|
originalSource: content,
|
||||||
source: content,
|
source: content,
|
||||||
inPre: false
|
inPre: false,
|
||||||
|
inVPre: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,11 +113,11 @@ function parseChildren(
|
|||||||
const s = context.source
|
const s = context.source
|
||||||
let node: TemplateChildNode | TemplateChildNode[] | undefined = undefined
|
let node: TemplateChildNode | TemplateChildNode[] | undefined = undefined
|
||||||
|
|
||||||
if (mode === TextModes.DATA) {
|
if (mode === TextModes.DATA || mode === TextModes.RCDATA) {
|
||||||
if (!context.inPre && startsWith(s, context.options.delimiters[0])) {
|
if (!context.inVPre && startsWith(s, context.options.delimiters[0])) {
|
||||||
// '{{'
|
// '{{'
|
||||||
node = parseInterpolation(context, mode)
|
node = parseInterpolation(context, mode)
|
||||||
} else if (s[0] === '<') {
|
} else if (mode === TextModes.DATA && s[0] === '<') {
|
||||||
// https://html.spec.whatwg.org/multipage/parsing.html#tag-open-state
|
// https://html.spec.whatwg.org/multipage/parsing.html#tag-open-state
|
||||||
if (s.length === 1) {
|
if (s.length === 1) {
|
||||||
emitError(context, ErrorCodes.EOF_BEFORE_TAG_NAME, 1)
|
emitError(context, ErrorCodes.EOF_BEFORE_TAG_NAME, 1)
|
||||||
@ -193,45 +189,51 @@ function parseChildren(
|
|||||||
// Whitespace management for more efficient output
|
// Whitespace management for more efficient output
|
||||||
// (same as v2 whitespace: 'condense')
|
// (same as v2 whitespace: 'condense')
|
||||||
let removedWhitespace = false
|
let removedWhitespace = false
|
||||||
if (
|
if (mode !== TextModes.RAWTEXT) {
|
||||||
mode !== TextModes.RAWTEXT &&
|
if (!context.inPre) {
|
||||||
(!parent || !context.options.isPreTag(parent.tag))
|
for (let i = 0; i < nodes.length; i++) {
|
||||||
) {
|
const node = nodes[i]
|
||||||
for (let i = 0; i < nodes.length; i++) {
|
if (node.type === NodeTypes.TEXT) {
|
||||||
const node = nodes[i]
|
if (!node.content.trim()) {
|
||||||
if (node.type === NodeTypes.TEXT) {
|
const prev = nodes[i - 1]
|
||||||
if (!node.content.trim()) {
|
const next = nodes[i + 1]
|
||||||
const prev = nodes[i - 1]
|
// If:
|
||||||
const next = nodes[i + 1]
|
// - the whitespace is the first or last node, or:
|
||||||
// If:
|
// - the whitespace is adjacent to a comment, or:
|
||||||
// - the whitespace is the first or last node, or:
|
// - the whitespace is between two elements AND contains newline
|
||||||
// - the whitespace is adjacent to a comment, or:
|
// Then the whitespace is ignored.
|
||||||
// - the whitespace is between two elements AND contains newline
|
if (
|
||||||
// Then the whitespace is ignored.
|
!prev ||
|
||||||
if (
|
!next ||
|
||||||
!prev ||
|
prev.type === NodeTypes.COMMENT ||
|
||||||
!next ||
|
next.type === NodeTypes.COMMENT ||
|
||||||
prev.type === NodeTypes.COMMENT ||
|
(prev.type === NodeTypes.ELEMENT &&
|
||||||
next.type === NodeTypes.COMMENT ||
|
next.type === NodeTypes.ELEMENT &&
|
||||||
(prev.type === NodeTypes.ELEMENT &&
|
/[\r\n]/.test(node.content))
|
||||||
next.type === NodeTypes.ELEMENT &&
|
) {
|
||||||
/[\r\n]/.test(node.content))
|
removedWhitespace = true
|
||||||
) {
|
nodes[i] = null as any
|
||||||
removedWhitespace = true
|
} else {
|
||||||
nodes[i] = null as any
|
// Otherwise, condensed consecutive whitespace inside the text down to
|
||||||
|
// a single space
|
||||||
|
node.content = ' '
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, condensed consecutive whitespace inside the text down to
|
node.content = node.content.replace(/\s+/g, ' ')
|
||||||
// a single space
|
|
||||||
node.content = ' '
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
node.content = node.content.replace(/\s+/g, ' ')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// remove leading newline per html spec
|
||||||
|
// https://html.spec.whatwg.org/multipage/grouping-content.html#the-pre-element
|
||||||
|
const first = nodes[0]
|
||||||
|
if (first && first.type === NodeTypes.TEXT) {
|
||||||
|
first.content = first.content.replace(/^\r?\n/, '')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return removedWhitespace ? nodes.filter(node => node !== null) : nodes
|
return removedWhitespace ? nodes.filter(Boolean) : nodes
|
||||||
}
|
}
|
||||||
|
|
||||||
function pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {
|
function pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {
|
||||||
@ -353,9 +355,11 @@ function parseElement(
|
|||||||
|
|
||||||
// Start tag.
|
// Start tag.
|
||||||
const wasInPre = context.inPre
|
const wasInPre = context.inPre
|
||||||
|
const wasInVPre = context.inVPre
|
||||||
const parent = last(ancestors)
|
const parent = last(ancestors)
|
||||||
const element = parseTag(context, TagType.Start, parent)
|
const element = parseTag(context, TagType.Start, parent)
|
||||||
const isPreBoundary = context.inPre && !wasInPre
|
const isPreBoundary = context.inPre && !wasInPre
|
||||||
|
const isVPreBoundary = context.inVPre && !wasInVPre
|
||||||
|
|
||||||
if (element.isSelfClosing || context.options.isVoidTag(element.tag)) {
|
if (element.isSelfClosing || context.options.isVoidTag(element.tag)) {
|
||||||
return element
|
return element
|
||||||
@ -363,7 +367,7 @@ function parseElement(
|
|||||||
|
|
||||||
// Children.
|
// Children.
|
||||||
ancestors.push(element)
|
ancestors.push(element)
|
||||||
const mode = context.options.getTextMode(element.tag, element.ns)
|
const mode = context.options.getTextMode(element.tag, element.ns, parent)
|
||||||
const children = parseChildren(context, mode, ancestors)
|
const children = parseChildren(context, mode, ancestors)
|
||||||
ancestors.pop()
|
ancestors.pop()
|
||||||
|
|
||||||
@ -373,7 +377,7 @@ function parseElement(
|
|||||||
if (startsWithEndTagOpen(context.source, element.tag)) {
|
if (startsWithEndTagOpen(context.source, element.tag)) {
|
||||||
parseTag(context, TagType.End, parent)
|
parseTag(context, TagType.End, parent)
|
||||||
} else {
|
} else {
|
||||||
emitError(context, ErrorCodes.X_MISSING_END_TAG)
|
emitError(context, ErrorCodes.X_MISSING_END_TAG, 0, element.loc.start)
|
||||||
if (context.source.length === 0 && element.tag.toLowerCase() === 'script') {
|
if (context.source.length === 0 && element.tag.toLowerCase() === 'script') {
|
||||||
const first = children[0]
|
const first = children[0]
|
||||||
if (first && startsWith(first.loc.source, '<!--')) {
|
if (first && startsWith(first.loc.source, '<!--')) {
|
||||||
@ -387,6 +391,9 @@ function parseElement(
|
|||||||
if (isPreBoundary) {
|
if (isPreBoundary) {
|
||||||
context.inPre = false
|
context.inPre = false
|
||||||
}
|
}
|
||||||
|
if (isVPreBoundary) {
|
||||||
|
context.inVPre = false
|
||||||
|
}
|
||||||
return element
|
return element
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -395,6 +402,10 @@ const enum TagType {
|
|||||||
End
|
End
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isSpecialTemplateDirective = /*#__PURE__*/ makeMap(
|
||||||
|
`if,else,else-if,for,slot`
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse a tag (E.g. `<div id=a>`) with that type (start tag or end tag).
|
* Parse a tag (E.g. `<div id=a>`) with that type (start tag or end tag).
|
||||||
*/
|
*/
|
||||||
@ -425,12 +436,17 @@ function parseTag(
|
|||||||
// Attributes.
|
// Attributes.
|
||||||
let props = parseAttributes(context, type)
|
let props = parseAttributes(context, type)
|
||||||
|
|
||||||
|
// check <pre> tag
|
||||||
|
if (context.options.isPreTag(tag)) {
|
||||||
|
context.inPre = true
|
||||||
|
}
|
||||||
|
|
||||||
// check v-pre
|
// check v-pre
|
||||||
if (
|
if (
|
||||||
!context.inPre &&
|
!context.inVPre &&
|
||||||
props.some(p => p.type === NodeTypes.DIRECTIVE && p.name === 'pre')
|
props.some(p => p.type === NodeTypes.DIRECTIVE && p.name === 'pre')
|
||||||
) {
|
) {
|
||||||
context.inPre = true
|
context.inVPre = true
|
||||||
// reset context
|
// reset context
|
||||||
extend(context, cursor)
|
extend(context, cursor)
|
||||||
context.source = currentSource
|
context.source = currentSource
|
||||||
@ -452,20 +468,32 @@ function parseTag(
|
|||||||
|
|
||||||
let tagType = ElementTypes.ELEMENT
|
let tagType = ElementTypes.ELEMENT
|
||||||
const options = context.options
|
const options = context.options
|
||||||
if (!context.inPre && !options.isCustomElement(tag)) {
|
if (!context.inVPre && !options.isCustomElement(tag)) {
|
||||||
if (options.isNativeTag) {
|
const hasVIs = props.some(
|
||||||
|
p => p.type === NodeTypes.DIRECTIVE && p.name === 'is'
|
||||||
|
)
|
||||||
|
if (options.isNativeTag && !hasVIs) {
|
||||||
if (!options.isNativeTag(tag)) tagType = ElementTypes.COMPONENT
|
if (!options.isNativeTag(tag)) tagType = ElementTypes.COMPONENT
|
||||||
} else if (
|
} else if (
|
||||||
|
hasVIs ||
|
||||||
isCoreComponent(tag) ||
|
isCoreComponent(tag) ||
|
||||||
(options.isBuiltInComponent && options.isBuiltInComponent(tag)) ||
|
(options.isBuiltInComponent && options.isBuiltInComponent(tag)) ||
|
||||||
/^[A-Z]/.test(tag)
|
/^[A-Z]/.test(tag) ||
|
||||||
|
tag === 'component'
|
||||||
) {
|
) {
|
||||||
tagType = ElementTypes.COMPONENT
|
tagType = ElementTypes.COMPONENT
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tag === 'slot') {
|
if (tag === 'slot') {
|
||||||
tagType = ElementTypes.SLOT
|
tagType = ElementTypes.SLOT
|
||||||
} else if (tag === 'template') {
|
} else if (
|
||||||
|
tag === 'template' &&
|
||||||
|
props.some(p => {
|
||||||
|
return (
|
||||||
|
p.type === NodeTypes.DIRECTIVE && isSpecialTemplateDirective(p.name)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
) {
|
||||||
tagType = ElementTypes.TEMPLATE
|
tagType = ElementTypes.TEMPLATE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -539,7 +567,7 @@ function parseAttribute(
|
|||||||
{
|
{
|
||||||
const pattern = /["'<]/g
|
const pattern = /["'<]/g
|
||||||
let m: RegExpExecArray | null
|
let m: RegExpExecArray | null
|
||||||
while ((m = pattern.exec(name)) !== null) {
|
while ((m = pattern.exec(name))) {
|
||||||
emitError(
|
emitError(
|
||||||
context,
|
context,
|
||||||
ErrorCodes.UNEXPECTED_CHARACTER_IN_ATTRIBUTE_NAME,
|
ErrorCodes.UNEXPECTED_CHARACTER_IN_ATTRIBUTE_NAME,
|
||||||
@ -570,7 +598,7 @@ function parseAttribute(
|
|||||||
}
|
}
|
||||||
const loc = getSelection(context, start)
|
const loc = getSelection(context, start)
|
||||||
|
|
||||||
if (!context.inPre && /^(v-|:|@|#)/.test(name)) {
|
if (!context.inVPre && /^(v-|:|@|#)/.test(name)) {
|
||||||
const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^@|^#)([^\.]+))?(.+)?$/i.exec(
|
const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^@|^#)([^\.]+))?(.+)?$/i.exec(
|
||||||
name
|
name
|
||||||
)!
|
)!
|
||||||
@ -688,9 +716,9 @@ function parseAttributeValue(
|
|||||||
if (!match) {
|
if (!match) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
let unexpectedChars = /["'<=`]/g
|
const unexpectedChars = /["'<=`]/g
|
||||||
let m: RegExpExecArray | null
|
let m: RegExpExecArray | null
|
||||||
while ((m = unexpectedChars.exec(match[0])) !== null) {
|
while ((m = unexpectedChars.exec(match[0]))) {
|
||||||
emitError(
|
emitError(
|
||||||
context,
|
context,
|
||||||
ErrorCodes.UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE,
|
ErrorCodes.UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE,
|
||||||
@ -818,8 +846,8 @@ function parseTextData(
|
|||||||
|
|
||||||
if (head[0] === '&') {
|
if (head[0] === '&') {
|
||||||
// Named character reference.
|
// Named character reference.
|
||||||
let name = '',
|
let name = ''
|
||||||
value: string | undefined = undefined
|
let value: string | undefined = undefined
|
||||||
if (/[0-9a-z]/i.test(rawText[1])) {
|
if (/[0-9a-z]/i.test(rawText[1])) {
|
||||||
for (
|
for (
|
||||||
let length = context.options.maxCRNameLength;
|
let length = context.options.maxCRNameLength;
|
||||||
@ -834,7 +862,7 @@ function parseTextData(
|
|||||||
if (
|
if (
|
||||||
mode === TextModes.ATTRIBUTE_VALUE &&
|
mode === TextModes.ATTRIBUTE_VALUE &&
|
||||||
!semi &&
|
!semi &&
|
||||||
/[=a-z0-9]/i.test(rawText[1 + name.length] || '')
|
/[=a-z0-9]/i.test(rawText[name.length + 1] || '')
|
||||||
) {
|
) {
|
||||||
decodedText += '&' + name
|
decodedText += '&' + name
|
||||||
advance(1 + name.length)
|
advance(1 + name.length)
|
||||||
@ -849,7 +877,6 @@ function parseTextData(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
emitError(context, ErrorCodes.UNKNOWN_NAMED_CHARACTER_REFERENCE)
|
|
||||||
decodedText += '&' + name
|
decodedText += '&' + name
|
||||||
advance(1 + name.length)
|
advance(1 + name.length)
|
||||||
}
|
}
|
||||||
@ -964,9 +991,9 @@ function getNewPosition(
|
|||||||
function emitError(
|
function emitError(
|
||||||
context: ParserContext,
|
context: ParserContext,
|
||||||
code: ErrorCodes,
|
code: ErrorCodes,
|
||||||
offset?: number
|
offset?: number,
|
||||||
|
loc: Position = getCursor(context)
|
||||||
): void {
|
): void {
|
||||||
const loc = getCursor(context)
|
|
||||||
if (offset) {
|
if (offset) {
|
||||||
loc.offset += offset
|
loc.offset += offset
|
||||||
loc.column += offset
|
loc.column += offset
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
export const FRAGMENT = Symbol(__DEV__ ? `Fragment` : ``)
|
export const FRAGMENT = Symbol(__DEV__ ? `Fragment` : ``)
|
||||||
export const PORTAL = Symbol(__DEV__ ? `Portal` : ``)
|
export const TELEPORT = Symbol(__DEV__ ? `Teleport` : ``)
|
||||||
export const SUSPENSE = Symbol(__DEV__ ? `Suspense` : ``)
|
export const SUSPENSE = Symbol(__DEV__ ? `Suspense` : ``)
|
||||||
export const KEEP_ALIVE = Symbol(__DEV__ ? `KeepAlive` : ``)
|
export const KEEP_ALIVE = Symbol(__DEV__ ? `KeepAlive` : ``)
|
||||||
export const BASE_TRANSITION = Symbol(__DEV__ ? `BaseTransition` : ``)
|
export const BASE_TRANSITION = Symbol(__DEV__ ? `BaseTransition` : ``)
|
||||||
@ -8,6 +8,7 @@ export const CREATE_BLOCK = Symbol(__DEV__ ? `createBlock` : ``)
|
|||||||
export const CREATE_VNODE = Symbol(__DEV__ ? `createVNode` : ``)
|
export const CREATE_VNODE = Symbol(__DEV__ ? `createVNode` : ``)
|
||||||
export const CREATE_COMMENT = Symbol(__DEV__ ? `createCommentVNode` : ``)
|
export const CREATE_COMMENT = Symbol(__DEV__ ? `createCommentVNode` : ``)
|
||||||
export const CREATE_TEXT = Symbol(__DEV__ ? `createTextVNode` : ``)
|
export const CREATE_TEXT = Symbol(__DEV__ ? `createTextVNode` : ``)
|
||||||
|
export const CREATE_STATIC = Symbol(__DEV__ ? `createStaticVNode` : ``)
|
||||||
export const RESOLVE_COMPONENT = Symbol(__DEV__ ? `resolveComponent` : ``)
|
export const RESOLVE_COMPONENT = Symbol(__DEV__ ? `resolveComponent` : ``)
|
||||||
export const RESOLVE_DYNAMIC_COMPONENT = Symbol(
|
export const RESOLVE_DYNAMIC_COMPONENT = Symbol(
|
||||||
__DEV__ ? `resolveDynamicComponent` : ``
|
__DEV__ ? `resolveDynamicComponent` : ``
|
||||||
@ -17,18 +18,22 @@ export const WITH_DIRECTIVES = Symbol(__DEV__ ? `withDirectives` : ``)
|
|||||||
export const RENDER_LIST = Symbol(__DEV__ ? `renderList` : ``)
|
export const RENDER_LIST = Symbol(__DEV__ ? `renderList` : ``)
|
||||||
export const RENDER_SLOT = Symbol(__DEV__ ? `renderSlot` : ``)
|
export const RENDER_SLOT = Symbol(__DEV__ ? `renderSlot` : ``)
|
||||||
export const CREATE_SLOTS = Symbol(__DEV__ ? `createSlots` : ``)
|
export const CREATE_SLOTS = Symbol(__DEV__ ? `createSlots` : ``)
|
||||||
export const TO_STRING = Symbol(__DEV__ ? `toString` : ``)
|
export const TO_DISPLAY_STRING = Symbol(__DEV__ ? `toDisplayString` : ``)
|
||||||
export const MERGE_PROPS = Symbol(__DEV__ ? `mergeProps` : ``)
|
export const MERGE_PROPS = Symbol(__DEV__ ? `mergeProps` : ``)
|
||||||
export const TO_HANDLERS = Symbol(__DEV__ ? `toHandlers` : ``)
|
export const TO_HANDLERS = Symbol(__DEV__ ? `toHandlers` : ``)
|
||||||
export const CAMELIZE = Symbol(__DEV__ ? `camelize` : ``)
|
export const CAMELIZE = Symbol(__DEV__ ? `camelize` : ``)
|
||||||
export const SET_BLOCK_TRACKING = Symbol(__DEV__ ? `setBlockTracking` : ``)
|
export const SET_BLOCK_TRACKING = Symbol(__DEV__ ? `setBlockTracking` : ``)
|
||||||
|
export const PUSH_SCOPE_ID = Symbol(__DEV__ ? `pushScopeId` : ``)
|
||||||
|
export const POP_SCOPE_ID = Symbol(__DEV__ ? `popScopeId` : ``)
|
||||||
|
export const WITH_SCOPE_ID = Symbol(__DEV__ ? `withScopeId` : ``)
|
||||||
|
export const WITH_CTX = Symbol(__DEV__ ? `withCtx` : ``)
|
||||||
|
|
||||||
// Name mapping for runtime helpers that need to be imported from 'vue' in
|
// Name mapping for runtime helpers that need to be imported from 'vue' in
|
||||||
// generated code. Make sure these are correctly exported in the runtime!
|
// generated code. Make sure these are correctly exported in the runtime!
|
||||||
// Using `any` here because TS doesn't allow symbols as index type.
|
// Using `any` here because TS doesn't allow symbols as index type.
|
||||||
export const helperNameMap: any = {
|
export const helperNameMap: any = {
|
||||||
[FRAGMENT]: `Fragment`,
|
[FRAGMENT]: `Fragment`,
|
||||||
[PORTAL]: `Portal`,
|
[TELEPORT]: `Teleport`,
|
||||||
[SUSPENSE]: `Suspense`,
|
[SUSPENSE]: `Suspense`,
|
||||||
[KEEP_ALIVE]: `KeepAlive`,
|
[KEEP_ALIVE]: `KeepAlive`,
|
||||||
[BASE_TRANSITION]: `BaseTransition`,
|
[BASE_TRANSITION]: `BaseTransition`,
|
||||||
@ -37,6 +42,7 @@ export const helperNameMap: any = {
|
|||||||
[CREATE_VNODE]: `createVNode`,
|
[CREATE_VNODE]: `createVNode`,
|
||||||
[CREATE_COMMENT]: `createCommentVNode`,
|
[CREATE_COMMENT]: `createCommentVNode`,
|
||||||
[CREATE_TEXT]: `createTextVNode`,
|
[CREATE_TEXT]: `createTextVNode`,
|
||||||
|
[CREATE_STATIC]: `createStaticVNode`,
|
||||||
[RESOLVE_COMPONENT]: `resolveComponent`,
|
[RESOLVE_COMPONENT]: `resolveComponent`,
|
||||||
[RESOLVE_DYNAMIC_COMPONENT]: `resolveDynamicComponent`,
|
[RESOLVE_DYNAMIC_COMPONENT]: `resolveDynamicComponent`,
|
||||||
[RESOLVE_DIRECTIVE]: `resolveDirective`,
|
[RESOLVE_DIRECTIVE]: `resolveDirective`,
|
||||||
@ -44,11 +50,15 @@ export const helperNameMap: any = {
|
|||||||
[RENDER_LIST]: `renderList`,
|
[RENDER_LIST]: `renderList`,
|
||||||
[RENDER_SLOT]: `renderSlot`,
|
[RENDER_SLOT]: `renderSlot`,
|
||||||
[CREATE_SLOTS]: `createSlots`,
|
[CREATE_SLOTS]: `createSlots`,
|
||||||
[TO_STRING]: `toString`,
|
[TO_DISPLAY_STRING]: `toDisplayString`,
|
||||||
[MERGE_PROPS]: `mergeProps`,
|
[MERGE_PROPS]: `mergeProps`,
|
||||||
[TO_HANDLERS]: `toHandlers`,
|
[TO_HANDLERS]: `toHandlers`,
|
||||||
[CAMELIZE]: `camelize`,
|
[CAMELIZE]: `camelize`,
|
||||||
[SET_BLOCK_TRACKING]: `setBlockTracking`
|
[SET_BLOCK_TRACKING]: `setBlockTracking`,
|
||||||
|
[PUSH_SCOPE_ID]: `pushScopeId`,
|
||||||
|
[POP_SCOPE_ID]: `popScopeId`,
|
||||||
|
[WITH_SCOPE_ID]: `withScopeId`,
|
||||||
|
[WITH_CTX]: `withCtx`
|
||||||
}
|
}
|
||||||
|
|
||||||
export function registerRuntimeHelpers(helpers: any) {
|
export function registerRuntimeHelpers(helpers: any) {
|
||||||
|
@ -12,11 +12,10 @@ import {
|
|||||||
JSChildNode,
|
JSChildNode,
|
||||||
SimpleExpressionNode,
|
SimpleExpressionNode,
|
||||||
ElementTypes,
|
ElementTypes,
|
||||||
ElementCodegenNode,
|
|
||||||
ComponentCodegenNode,
|
|
||||||
createCallExpression,
|
|
||||||
CacheExpression,
|
CacheExpression,
|
||||||
createCacheExpression
|
createCacheExpression,
|
||||||
|
TemplateLiteral,
|
||||||
|
createVNodeCall
|
||||||
} from './ast'
|
} from './ast'
|
||||||
import {
|
import {
|
||||||
isString,
|
isString,
|
||||||
@ -27,14 +26,14 @@ import {
|
|||||||
} from '@vue/shared'
|
} from '@vue/shared'
|
||||||
import { defaultOnError } from './errors'
|
import { defaultOnError } from './errors'
|
||||||
import {
|
import {
|
||||||
TO_STRING,
|
TO_DISPLAY_STRING,
|
||||||
FRAGMENT,
|
FRAGMENT,
|
||||||
helperNameMap,
|
helperNameMap,
|
||||||
WITH_DIRECTIVES,
|
|
||||||
CREATE_BLOCK,
|
CREATE_BLOCK,
|
||||||
CREATE_COMMENT
|
CREATE_COMMENT,
|
||||||
|
OPEN_BLOCK
|
||||||
} from './runtimeHelpers'
|
} from './runtimeHelpers'
|
||||||
import { isVSlot, createBlockExpression } from './utils'
|
import { isVSlot } from './utils'
|
||||||
import { hoistStatic, isSingleElementRoot } from './transforms/hoistStatic'
|
import { hoistStatic, isSingleElementRoot } from './transforms/hoistStatic'
|
||||||
|
|
||||||
// There are two types of transforms:
|
// There are two types of transforms:
|
||||||
@ -61,7 +60,8 @@ export type DirectiveTransform = (
|
|||||||
|
|
||||||
export interface DirectiveTransformResult {
|
export interface DirectiveTransformResult {
|
||||||
props: Property[]
|
props: Property[]
|
||||||
needRuntime: boolean | symbol
|
needRuntime?: boolean | symbol
|
||||||
|
ssrTagParts?: TemplateLiteral['elements']
|
||||||
}
|
}
|
||||||
|
|
||||||
// A structural directive transform is a technically a NodeTransform;
|
// A structural directive transform is a technically a NodeTransform;
|
||||||
@ -84,6 +84,7 @@ export interface TransformContext extends Required<TransformOptions> {
|
|||||||
directives: Set<string>
|
directives: Set<string>
|
||||||
hoists: JSChildNode[]
|
hoists: JSChildNode[]
|
||||||
imports: Set<ImportItem>
|
imports: Set<ImportItem>
|
||||||
|
temps: number
|
||||||
cached: number
|
cached: number
|
||||||
identifiers: { [name: string]: number | undefined }
|
identifiers: { [name: string]: number | undefined }
|
||||||
scopes: {
|
scopes: {
|
||||||
@ -106,7 +107,7 @@ export interface TransformContext extends Required<TransformOptions> {
|
|||||||
cache<T extends JSChildNode>(exp: T, isVNode?: boolean): CacheExpression | T
|
cache<T extends JSChildNode>(exp: T, isVNode?: boolean): CacheExpression | T
|
||||||
}
|
}
|
||||||
|
|
||||||
function createTransformContext(
|
export function createTransformContext(
|
||||||
root: RootNode,
|
root: RootNode,
|
||||||
{
|
{
|
||||||
prefixIdentifiers = false,
|
prefixIdentifiers = false,
|
||||||
@ -114,17 +115,36 @@ function createTransformContext(
|
|||||||
cacheHandlers = false,
|
cacheHandlers = false,
|
||||||
nodeTransforms = [],
|
nodeTransforms = [],
|
||||||
directiveTransforms = {},
|
directiveTransforms = {},
|
||||||
|
transformHoist = null,
|
||||||
isBuiltInComponent = NOOP,
|
isBuiltInComponent = NOOP,
|
||||||
|
expressionPlugins = [],
|
||||||
|
scopeId = null,
|
||||||
|
ssr = false,
|
||||||
onError = defaultOnError
|
onError = defaultOnError
|
||||||
}: TransformOptions
|
}: TransformOptions
|
||||||
): TransformContext {
|
): TransformContext {
|
||||||
const context: TransformContext = {
|
const context: TransformContext = {
|
||||||
|
// options
|
||||||
|
prefixIdentifiers,
|
||||||
|
hoistStatic,
|
||||||
|
cacheHandlers,
|
||||||
|
nodeTransforms,
|
||||||
|
directiveTransforms,
|
||||||
|
transformHoist,
|
||||||
|
isBuiltInComponent,
|
||||||
|
expressionPlugins,
|
||||||
|
scopeId,
|
||||||
|
ssr,
|
||||||
|
onError,
|
||||||
|
|
||||||
|
// state
|
||||||
root,
|
root,
|
||||||
helpers: new Set(),
|
helpers: new Set(),
|
||||||
components: new Set(),
|
components: new Set(),
|
||||||
directives: new Set(),
|
directives: new Set(),
|
||||||
hoists: [],
|
hoists: [],
|
||||||
imports: new Set(),
|
imports: new Set(),
|
||||||
|
temps: 0,
|
||||||
cached: 0,
|
cached: 0,
|
||||||
identifiers: {},
|
identifiers: {},
|
||||||
scopes: {
|
scopes: {
|
||||||
@ -133,25 +153,17 @@ function createTransformContext(
|
|||||||
vPre: 0,
|
vPre: 0,
|
||||||
vOnce: 0
|
vOnce: 0
|
||||||
},
|
},
|
||||||
prefixIdentifiers,
|
|
||||||
hoistStatic,
|
|
||||||
cacheHandlers,
|
|
||||||
nodeTransforms,
|
|
||||||
directiveTransforms,
|
|
||||||
isBuiltInComponent,
|
|
||||||
onError,
|
|
||||||
parent: null,
|
parent: null,
|
||||||
currentNode: root,
|
currentNode: root,
|
||||||
childIndex: 0,
|
childIndex: 0,
|
||||||
|
|
||||||
|
// methods
|
||||||
helper(name) {
|
helper(name) {
|
||||||
context.helpers.add(name)
|
context.helpers.add(name)
|
||||||
return name
|
return name
|
||||||
},
|
},
|
||||||
helperString(name) {
|
helperString(name) {
|
||||||
return (
|
return `_${helperNameMap[context.helper(name)]}`
|
||||||
(context.prefixIdentifiers ? `` : `_`) +
|
|
||||||
helperNameMap[context.helper(name)]
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
replaceNode(node) {
|
replaceNode(node) {
|
||||||
/* istanbul ignore if */
|
/* istanbul ignore if */
|
||||||
@ -251,10 +263,20 @@ export function transform(root: RootNode, options: TransformOptions) {
|
|||||||
if (options.hoistStatic) {
|
if (options.hoistStatic) {
|
||||||
hoistStatic(root, context)
|
hoistStatic(root, context)
|
||||||
}
|
}
|
||||||
finalizeRoot(root, context)
|
if (!options.ssr) {
|
||||||
|
createRootCodegen(root, context)
|
||||||
|
}
|
||||||
|
// finalize meta information
|
||||||
|
root.helpers = [...context.helpers]
|
||||||
|
root.components = [...context.components]
|
||||||
|
root.directives = [...context.directives]
|
||||||
|
root.imports = [...context.imports]
|
||||||
|
root.hoists = context.hoists
|
||||||
|
root.temps = context.temps
|
||||||
|
root.cached = context.cached
|
||||||
}
|
}
|
||||||
|
|
||||||
function finalizeRoot(root: RootNode, context: TransformContext) {
|
function createRootCodegen(root: RootNode, context: TransformContext) {
|
||||||
const { helper } = context
|
const { helper } = context
|
||||||
const { children } = root
|
const { children } = root
|
||||||
const child = children[0]
|
const child = children[0]
|
||||||
@ -263,20 +285,13 @@ function finalizeRoot(root: RootNode, context: TransformContext) {
|
|||||||
if (isSingleElementRoot(root, child) && child.codegenNode) {
|
if (isSingleElementRoot(root, child) && child.codegenNode) {
|
||||||
// single element root is never hoisted so codegenNode will never be
|
// single element root is never hoisted so codegenNode will never be
|
||||||
// SimpleExpressionNode
|
// SimpleExpressionNode
|
||||||
const codegenNode = child.codegenNode as
|
const codegenNode = child.codegenNode
|
||||||
| ElementCodegenNode
|
if (codegenNode.type === NodeTypes.VNODE_CALL) {
|
||||||
| ComponentCodegenNode
|
codegenNode.isBlock = true
|
||||||
| CacheExpression
|
helper(OPEN_BLOCK)
|
||||||
if (codegenNode.type !== NodeTypes.JS_CACHE_EXPRESSION) {
|
helper(CREATE_BLOCK)
|
||||||
if (codegenNode.callee === WITH_DIRECTIVES) {
|
|
||||||
codegenNode.arguments[0].callee = helper(CREATE_BLOCK)
|
|
||||||
} else {
|
|
||||||
codegenNode.callee = helper(CREATE_BLOCK)
|
|
||||||
}
|
|
||||||
root.codegenNode = createBlockExpression(codegenNode, context)
|
|
||||||
} else {
|
|
||||||
root.codegenNode = codegenNode
|
|
||||||
}
|
}
|
||||||
|
root.codegenNode = codegenNode
|
||||||
} else {
|
} else {
|
||||||
// - single <slot/>, IfNode, ForNode: already blocks.
|
// - single <slot/>, IfNode, ForNode: already blocks.
|
||||||
// - single text node: always patched.
|
// - single text node: always patched.
|
||||||
@ -285,27 +300,21 @@ function finalizeRoot(root: RootNode, context: TransformContext) {
|
|||||||
}
|
}
|
||||||
} else if (children.length > 1) {
|
} else if (children.length > 1) {
|
||||||
// root has multiple nodes - return a fragment block.
|
// root has multiple nodes - return a fragment block.
|
||||||
root.codegenNode = createBlockExpression(
|
root.codegenNode = createVNodeCall(
|
||||||
createCallExpression(helper(CREATE_BLOCK), [
|
context,
|
||||||
helper(FRAGMENT),
|
helper(FRAGMENT),
|
||||||
`null`,
|
undefined,
|
||||||
root.children,
|
root.children,
|
||||||
`${PatchFlags.STABLE_FRAGMENT} /* ${
|
`${PatchFlags.STABLE_FRAGMENT} /* ${
|
||||||
PatchFlagNames[PatchFlags.STABLE_FRAGMENT]
|
PatchFlagNames[PatchFlags.STABLE_FRAGMENT]
|
||||||
} */`
|
} */`,
|
||||||
]),
|
undefined,
|
||||||
context
|
undefined,
|
||||||
|
true
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
// no children = noop. codegen will return null.
|
// no children = noop. codegen will return null.
|
||||||
}
|
}
|
||||||
// finalize meta information
|
|
||||||
root.helpers = [...context.helpers]
|
|
||||||
root.components = [...context.components]
|
|
||||||
root.directives = [...context.directives]
|
|
||||||
root.imports = [...context.imports]
|
|
||||||
root.hoists = context.hoists
|
|
||||||
root.cached = context.cached
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function traverseChildren(
|
export function traverseChildren(
|
||||||
@ -319,7 +328,6 @@ export function traverseChildren(
|
|||||||
for (; i < parent.children.length; i++) {
|
for (; i < parent.children.length; i++) {
|
||||||
const child = parent.children[i]
|
const child = parent.children[i]
|
||||||
if (isString(child)) continue
|
if (isString(child)) continue
|
||||||
context.currentNode = child
|
|
||||||
context.parent = parent
|
context.parent = parent
|
||||||
context.childIndex = i
|
context.childIndex = i
|
||||||
context.onNodeRemoved = nodeRemoved
|
context.onNodeRemoved = nodeRemoved
|
||||||
@ -331,6 +339,7 @@ export function traverseNode(
|
|||||||
node: RootNode | TemplateChildNode,
|
node: RootNode | TemplateChildNode,
|
||||||
context: TransformContext
|
context: TransformContext
|
||||||
) {
|
) {
|
||||||
|
context.currentNode = node
|
||||||
// apply transform plugins
|
// apply transform plugins
|
||||||
const { nodeTransforms } = context
|
const { nodeTransforms } = context
|
||||||
const exitFns = []
|
const exitFns = []
|
||||||
@ -354,21 +363,26 @@ export function traverseNode(
|
|||||||
|
|
||||||
switch (node.type) {
|
switch (node.type) {
|
||||||
case NodeTypes.COMMENT:
|
case NodeTypes.COMMENT:
|
||||||
// inject import for the Comment symbol, which is needed for creating
|
if (!context.ssr) {
|
||||||
// comment nodes with `createVNode`
|
// inject import for the Comment symbol, which is needed for creating
|
||||||
context.helper(CREATE_COMMENT)
|
// comment nodes with `createVNode`
|
||||||
|
context.helper(CREATE_COMMENT)
|
||||||
|
}
|
||||||
break
|
break
|
||||||
case NodeTypes.INTERPOLATION:
|
case NodeTypes.INTERPOLATION:
|
||||||
// no need to traverse, but we need to inject toString helper
|
// no need to traverse, but we need to inject toString helper
|
||||||
context.helper(TO_STRING)
|
if (!context.ssr) {
|
||||||
|
context.helper(TO_DISPLAY_STRING)
|
||||||
|
}
|
||||||
break
|
break
|
||||||
|
|
||||||
// for container types, further traverse downwards
|
// for container types, further traverse downwards
|
||||||
case NodeTypes.IF:
|
case NodeTypes.IF:
|
||||||
for (let i = 0; i < node.branches.length; i++) {
|
for (let i = 0; i < node.branches.length; i++) {
|
||||||
traverseChildren(node.branches[i], context)
|
traverseNode(node.branches[i], context)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
case NodeTypes.IF_BRANCH:
|
||||||
case NodeTypes.FOR:
|
case NodeTypes.FOR:
|
||||||
case NodeTypes.ELEMENT:
|
case NodeTypes.ELEMENT:
|
||||||
case NodeTypes.ROOT:
|
case NodeTypes.ROOT:
|
||||||
|
@ -8,11 +8,9 @@ import {
|
|||||||
ComponentNode,
|
ComponentNode,
|
||||||
TemplateNode,
|
TemplateNode,
|
||||||
ElementNode,
|
ElementNode,
|
||||||
PlainElementCodegenNode,
|
VNodeCall
|
||||||
CodegenNodeWithDirective
|
|
||||||
} from '../ast'
|
} from '../ast'
|
||||||
import { TransformContext } from '../transform'
|
import { TransformContext } from '../transform'
|
||||||
import { WITH_DIRECTIVES } from '../runtimeHelpers'
|
|
||||||
import { PatchFlags, isString, isSymbol } from '@vue/shared'
|
import { PatchFlags, isString, isSymbol } from '@vue/shared'
|
||||||
import { isSlotOutlet, findProp } from '../utils'
|
import { isSlotOutlet, findProp } from '../utils'
|
||||||
|
|
||||||
@ -21,6 +19,8 @@ export function hoistStatic(root: RootNode, context: TransformContext) {
|
|||||||
root.children,
|
root.children,
|
||||||
context,
|
context,
|
||||||
new Map(),
|
new Map(),
|
||||||
|
// Root node is unfortuantely non-hoistable due to potential parent
|
||||||
|
// fallthrough attributes.
|
||||||
isSingleElementRoot(root, root.children[0])
|
isSingleElementRoot(root, root.children[0])
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -52,13 +52,18 @@ function walk(
|
|||||||
) {
|
) {
|
||||||
if (!doNotHoistNode && isStaticNode(child, resultCache)) {
|
if (!doNotHoistNode && isStaticNode(child, resultCache)) {
|
||||||
// whole tree is static
|
// whole tree is static
|
||||||
child.codegenNode = context.hoist(child.codegenNode!)
|
;(child.codegenNode as VNodeCall).patchFlag =
|
||||||
|
PatchFlags.HOISTED + (__DEV__ ? ` /* HOISTED */` : ``)
|
||||||
|
const hoisted = context.transformHoist
|
||||||
|
? context.transformHoist(child, context)
|
||||||
|
: child.codegenNode!
|
||||||
|
child.codegenNode = context.hoist(hoisted)
|
||||||
continue
|
continue
|
||||||
} else {
|
} else {
|
||||||
// node may contain dynamic children, but its props may be eligible for
|
// node may contain dynamic children, but its props may be eligible for
|
||||||
// hoisting.
|
// hoisting.
|
||||||
const codegenNode = child.codegenNode!
|
const codegenNode = child.codegenNode!
|
||||||
if (codegenNode.type === NodeTypes.JS_CALL_EXPRESSION) {
|
if (codegenNode.type === NodeTypes.VNODE_CALL) {
|
||||||
const flag = getPatchFlag(codegenNode)
|
const flag = getPatchFlag(codegenNode)
|
||||||
if (
|
if (
|
||||||
(!flag ||
|
(!flag ||
|
||||||
@ -68,8 +73,8 @@ function walk(
|
|||||||
!hasCachedProps(child)
|
!hasCachedProps(child)
|
||||||
) {
|
) {
|
||||||
const props = getNodeProps(child)
|
const props = getNodeProps(child)
|
||||||
if (props && props !== `null`) {
|
if (props) {
|
||||||
getVNodeCall(codegenNode).arguments[1] = context.hoist(props)
|
codegenNode.props = context.hoist(props)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -86,6 +91,11 @@ function walk(
|
|||||||
// Do not hoist v-if single child because it has to be a block
|
// Do not hoist v-if single child because it has to be a block
|
||||||
walk(branchChildren, context, resultCache, branchChildren.length === 1)
|
walk(branchChildren, context, resultCache, branchChildren.length === 1)
|
||||||
}
|
}
|
||||||
|
} else if (
|
||||||
|
child.type === NodeTypes.TEXT_CALL &&
|
||||||
|
isStaticNode(child.content, resultCache)
|
||||||
|
) {
|
||||||
|
child.codegenNode = context.hoist(child.codegenNode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -104,7 +114,7 @@ export function isStaticNode(
|
|||||||
return cached
|
return cached
|
||||||
}
|
}
|
||||||
const codegenNode = node.codegenNode!
|
const codegenNode = node.codegenNode!
|
||||||
if (codegenNode.type !== NodeTypes.JS_CALL_EXPRESSION) {
|
if (codegenNode.type !== NodeTypes.VNODE_CALL) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
const flag = getPatchFlag(codegenNode)
|
const flag = getPatchFlag(codegenNode)
|
||||||
@ -116,6 +126,12 @@ export function isStaticNode(
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// only svg/foreignObject could be block here, however if they are static
|
||||||
|
// then they don't need to be blocks since there will be no nested
|
||||||
|
// updates.
|
||||||
|
if (codegenNode.isBlock) {
|
||||||
|
codegenNode.isBlock = false
|
||||||
|
}
|
||||||
resultCache.set(node, true)
|
resultCache.set(node, true)
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
@ -127,6 +143,7 @@ export function isStaticNode(
|
|||||||
return true
|
return true
|
||||||
case NodeTypes.IF:
|
case NodeTypes.IF:
|
||||||
case NodeTypes.FOR:
|
case NodeTypes.FOR:
|
||||||
|
case NodeTypes.IF_BRANCH:
|
||||||
return false
|
return false
|
||||||
case NodeTypes.INTERPOLATION:
|
case NodeTypes.INTERPOLATION:
|
||||||
case NodeTypes.TEXT_CALL:
|
case NodeTypes.TEXT_CALL:
|
||||||
@ -157,14 +174,20 @@ function hasCachedProps(node: PlainElementNode): boolean {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
const props = getNodeProps(node)
|
const props = getNodeProps(node)
|
||||||
if (
|
if (props && props.type === NodeTypes.JS_OBJECT_EXPRESSION) {
|
||||||
props &&
|
|
||||||
props !== 'null' &&
|
|
||||||
props.type === NodeTypes.JS_OBJECT_EXPRESSION
|
|
||||||
) {
|
|
||||||
const { properties } = props
|
const { properties } = props
|
||||||
for (let i = 0; i < properties.length; i++) {
|
for (let i = 0; i < properties.length; i++) {
|
||||||
if (properties[i].value.type === NodeTypes.JS_CACHE_EXPRESSION) {
|
const val = properties[i].value
|
||||||
|
if (val.type === NodeTypes.JS_CACHE_EXPRESSION) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// merged event handlers
|
||||||
|
if (
|
||||||
|
val.type === NodeTypes.JS_ARRAY_EXPRESSION &&
|
||||||
|
val.elements.some(
|
||||||
|
e => !isString(e) && e.type === NodeTypes.JS_CACHE_EXPRESSION
|
||||||
|
)
|
||||||
|
) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -174,30 +197,12 @@ function hasCachedProps(node: PlainElementNode): boolean {
|
|||||||
|
|
||||||
function getNodeProps(node: PlainElementNode) {
|
function getNodeProps(node: PlainElementNode) {
|
||||||
const codegenNode = node.codegenNode!
|
const codegenNode = node.codegenNode!
|
||||||
if (codegenNode.type === NodeTypes.JS_CALL_EXPRESSION) {
|
if (codegenNode.type === NodeTypes.VNODE_CALL) {
|
||||||
return getVNodeArgAt(
|
return codegenNode.props
|
||||||
codegenNode,
|
|
||||||
1
|
|
||||||
) as PlainElementCodegenNode['arguments'][1]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type NonCachedCodegenNode =
|
function getPatchFlag(node: VNodeCall): number | undefined {
|
||||||
| PlainElementCodegenNode
|
const flag = node.patchFlag
|
||||||
| CodegenNodeWithDirective<PlainElementCodegenNode>
|
|
||||||
|
|
||||||
function getVNodeArgAt(
|
|
||||||
node: NonCachedCodegenNode,
|
|
||||||
index: number
|
|
||||||
): PlainElementCodegenNode['arguments'][number] {
|
|
||||||
return getVNodeCall(node).arguments[index]
|
|
||||||
}
|
|
||||||
|
|
||||||
function getVNodeCall(node: NonCachedCodegenNode) {
|
|
||||||
return node.callee === WITH_DIRECTIVES ? node.arguments[0] : node
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPatchFlag(node: NonCachedCodegenNode): number | undefined {
|
|
||||||
const flag = getVNodeArgAt(node, 3) as string
|
|
||||||
return flag ? parseInt(flag, 10) : undefined
|
return flag ? parseInt(flag, 10) : undefined
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
import { DirectiveTransform } from '../transform'
|
||||||
|
|
||||||
|
export const noopDirectiveTransform: DirectiveTransform = () => ({ props: [] })
|
@ -13,27 +13,31 @@ import {
|
|||||||
createObjectProperty,
|
createObjectProperty,
|
||||||
createSimpleExpression,
|
createSimpleExpression,
|
||||||
createObjectExpression,
|
createObjectExpression,
|
||||||
Property
|
Property,
|
||||||
|
ComponentNode,
|
||||||
|
VNodeCall,
|
||||||
|
TemplateTextChildNode,
|
||||||
|
DirectiveArguments,
|
||||||
|
createVNodeCall
|
||||||
} from '../ast'
|
} from '../ast'
|
||||||
import { PatchFlags, PatchFlagNames, isSymbol } from '@vue/shared'
|
import { PatchFlags, PatchFlagNames, isSymbol, isOn } from '@vue/shared'
|
||||||
import { createCompilerError, ErrorCodes } from '../errors'
|
import { createCompilerError, ErrorCodes } from '../errors'
|
||||||
import {
|
import {
|
||||||
CREATE_VNODE,
|
|
||||||
WITH_DIRECTIVES,
|
|
||||||
RESOLVE_DIRECTIVE,
|
RESOLVE_DIRECTIVE,
|
||||||
RESOLVE_COMPONENT,
|
RESOLVE_COMPONENT,
|
||||||
RESOLVE_DYNAMIC_COMPONENT,
|
RESOLVE_DYNAMIC_COMPONENT,
|
||||||
MERGE_PROPS,
|
MERGE_PROPS,
|
||||||
TO_HANDLERS,
|
TO_HANDLERS,
|
||||||
PORTAL,
|
TELEPORT,
|
||||||
KEEP_ALIVE
|
KEEP_ALIVE
|
||||||
} from '../runtimeHelpers'
|
} from '../runtimeHelpers'
|
||||||
import {
|
import {
|
||||||
getInnerRange,
|
getInnerRange,
|
||||||
isVSlot,
|
|
||||||
toValidAssetId,
|
toValidAssetId,
|
||||||
findProp,
|
findProp,
|
||||||
isCoreComponent
|
isCoreComponent,
|
||||||
|
isBindKey,
|
||||||
|
findDir
|
||||||
} from '../utils'
|
} from '../utils'
|
||||||
import { buildSlots } from './vSlot'
|
import { buildSlots } from './vSlot'
|
||||||
import { isStaticNode } from './hoistStatic'
|
import { isStaticNode } from './hoistStatic'
|
||||||
@ -45,107 +49,97 @@ const directiveImportMap = new WeakMap<DirectiveNode, symbol>()
|
|||||||
// generate a JavaScript AST for this element's codegen
|
// generate a JavaScript AST for this element's codegen
|
||||||
export const transformElement: NodeTransform = (node, context) => {
|
export const transformElement: NodeTransform = (node, context) => {
|
||||||
if (
|
if (
|
||||||
node.type !== NodeTypes.ELEMENT ||
|
!(
|
||||||
// handled by transformSlotOutlet
|
node.type === NodeTypes.ELEMENT &&
|
||||||
node.tagType === ElementTypes.SLOT ||
|
(node.tagType === ElementTypes.ELEMENT ||
|
||||||
// <template v-if/v-for> should have already been replaced
|
node.tagType === ElementTypes.COMPONENT)
|
||||||
// <template v-slot> is handled by buildSlots
|
)
|
||||||
(node.tagType === ElementTypes.TEMPLATE && node.props.some(isVSlot))
|
|
||||||
) {
|
) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// perform the work on exit, after all child expressions have been
|
// perform the work on exit, after all child expressions have been
|
||||||
// processed and merged.
|
// processed and merged.
|
||||||
return function postTransformElement() {
|
return function postTransformElement() {
|
||||||
const { tag, tagType, props } = node
|
const { tag, props } = node
|
||||||
const builtInComponentSymbol =
|
const isComponent = node.tagType === ElementTypes.COMPONENT
|
||||||
isCoreComponent(tag) || context.isBuiltInComponent(tag)
|
|
||||||
const isComponent = tagType === ElementTypes.COMPONENT
|
|
||||||
|
|
||||||
let hasProps = props.length > 0
|
// The goal of the transform is to create a codegenNode implementing the
|
||||||
|
// VNodeCall interface.
|
||||||
|
const vnodeTag = isComponent
|
||||||
|
? resolveComponentType(node as ComponentNode, context)
|
||||||
|
: `"${tag}"`
|
||||||
|
|
||||||
|
let vnodeProps: VNodeCall['props']
|
||||||
|
let vnodeChildren: VNodeCall['children']
|
||||||
|
let vnodePatchFlag: VNodeCall['patchFlag']
|
||||||
let patchFlag: number = 0
|
let patchFlag: number = 0
|
||||||
let runtimeDirectives: DirectiveNode[] | undefined
|
let vnodeDynamicProps: VNodeCall['dynamicProps']
|
||||||
let dynamicPropNames: string[] | undefined
|
let dynamicPropNames: string[] | undefined
|
||||||
let dynamicComponent: string | CallExpression | undefined
|
let vnodeDirectives: VNodeCall['directives']
|
||||||
|
|
||||||
// handle dynamic component
|
let shouldUseBlock =
|
||||||
const isProp = findProp(node, 'is')
|
!isComponent &&
|
||||||
if (tag === 'component') {
|
// <svg> and <foreignObject> must be forced into blocks so that block
|
||||||
if (isProp) {
|
// updates inside get proper isSVG flag at runtime. (#639, #643)
|
||||||
// static <component is="foo" />
|
// This is technically web-specific, but splitting the logic out of core
|
||||||
if (isProp.type === NodeTypes.ATTRIBUTE) {
|
// leads to too much unnecessary complexity.
|
||||||
const tag = isProp.value && isProp.value.content
|
(tag === 'svg' ||
|
||||||
if (tag) {
|
tag === 'foreignObject' ||
|
||||||
context.helper(RESOLVE_COMPONENT)
|
// #938: elements with dynamic keys should be forced into blocks
|
||||||
context.components.add(tag)
|
findProp(node, 'key', true))
|
||||||
dynamicComponent = toValidAssetId(tag, `component`)
|
|
||||||
}
|
// props
|
||||||
}
|
if (props.length > 0) {
|
||||||
// dynamic <component :is="asdf" />
|
const propsBuildResult = buildProps(node, context)
|
||||||
else if (isProp.exp) {
|
vnodeProps = propsBuildResult.props
|
||||||
dynamicComponent = createCallExpression(
|
patchFlag = propsBuildResult.patchFlag
|
||||||
context.helper(RESOLVE_DYNAMIC_COMPONENT),
|
dynamicPropNames = propsBuildResult.dynamicPropNames
|
||||||
// _ctx.$ exposes the owner instance of current render function
|
const directives = propsBuildResult.directives
|
||||||
[isProp.exp, context.prefixIdentifiers ? `_ctx.$` : `$`]
|
vnodeDirectives =
|
||||||
|
directives && directives.length
|
||||||
|
? (createArrayExpression(
|
||||||
|
directives.map(dir => buildDirectiveArgs(dir, context))
|
||||||
|
) as DirectiveArguments)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
// children
|
||||||
|
if (node.children.length > 0) {
|
||||||
|
if (vnodeTag === KEEP_ALIVE) {
|
||||||
|
// Although a built-in component, we compile KeepAlive with raw children
|
||||||
|
// instead of slot functions so that it can be used inside Transition
|
||||||
|
// or other Transition-wrapping HOCs.
|
||||||
|
// To ensure correct updates with block optimizations, we need to:
|
||||||
|
// 1. Force keep-alive into a block. This avoids its children being
|
||||||
|
// collected by a parent block.
|
||||||
|
shouldUseBlock = true
|
||||||
|
// 2. Force keep-alive to always be updated, since it uses raw children.
|
||||||
|
patchFlag |= PatchFlags.DYNAMIC_SLOTS
|
||||||
|
if (__DEV__ && node.children.length > 1) {
|
||||||
|
context.onError(
|
||||||
|
createCompilerError(ErrorCodes.X_KEEP_ALIVE_INVALID_CHILDREN, {
|
||||||
|
start: node.children[0].loc.start,
|
||||||
|
end: node.children[node.children.length - 1].loc.end,
|
||||||
|
source: ''
|
||||||
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let nodeType
|
const shouldBuildAsSlots =
|
||||||
if (dynamicComponent) {
|
|
||||||
nodeType = dynamicComponent
|
|
||||||
} else if (builtInComponentSymbol) {
|
|
||||||
nodeType = context.helper(builtInComponentSymbol)
|
|
||||||
} else if (isComponent) {
|
|
||||||
// user component w/ resolve
|
|
||||||
context.helper(RESOLVE_COMPONENT)
|
|
||||||
context.components.add(tag)
|
|
||||||
nodeType = toValidAssetId(tag, `component`)
|
|
||||||
} else {
|
|
||||||
// plain element
|
|
||||||
nodeType = `"${node.tag}"`
|
|
||||||
}
|
|
||||||
|
|
||||||
const args: CallExpression['arguments'] = [nodeType]
|
|
||||||
// props
|
|
||||||
if (hasProps) {
|
|
||||||
const propsBuildResult = buildProps(
|
|
||||||
node,
|
|
||||||
context,
|
|
||||||
// skip reserved "is" prop <component is>
|
|
||||||
isProp ? node.props.filter(p => p !== isProp) : node.props
|
|
||||||
)
|
|
||||||
patchFlag = propsBuildResult.patchFlag
|
|
||||||
dynamicPropNames = propsBuildResult.dynamicPropNames
|
|
||||||
runtimeDirectives = propsBuildResult.directives
|
|
||||||
if (!propsBuildResult.props) {
|
|
||||||
hasProps = false
|
|
||||||
} else {
|
|
||||||
args.push(propsBuildResult.props)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// children
|
|
||||||
const hasChildren = node.children.length > 0
|
|
||||||
if (hasChildren) {
|
|
||||||
if (!hasProps) {
|
|
||||||
args.push(`null`)
|
|
||||||
}
|
|
||||||
// Portal & KeepAlive should have normal children instead of slots
|
|
||||||
// Portal is not a real component has dedicated handling in the renderer
|
|
||||||
// KeepAlive should not track its own deps so that it can be used inside
|
|
||||||
// Transition
|
|
||||||
if (
|
|
||||||
isComponent &&
|
isComponent &&
|
||||||
builtInComponentSymbol !== PORTAL &&
|
// Teleport is not a real component and has dedicated runtime handling
|
||||||
builtInComponentSymbol !== KEEP_ALIVE
|
vnodeTag !== TELEPORT &&
|
||||||
) {
|
// explained above.
|
||||||
|
vnodeTag !== KEEP_ALIVE
|
||||||
|
|
||||||
|
if (shouldBuildAsSlots) {
|
||||||
const { slots, hasDynamicSlots } = buildSlots(node, context)
|
const { slots, hasDynamicSlots } = buildSlots(node, context)
|
||||||
args.push(slots)
|
vnodeChildren = slots
|
||||||
if (hasDynamicSlots) {
|
if (hasDynamicSlots) {
|
||||||
patchFlag |= PatchFlags.DYNAMIC_SLOTS
|
patchFlag |= PatchFlags.DYNAMIC_SLOTS
|
||||||
}
|
}
|
||||||
} else if (node.children.length === 1) {
|
} else if (node.children.length === 1 && vnodeTag !== TELEPORT) {
|
||||||
const child = node.children[0]
|
const child = node.children[0]
|
||||||
const type = child.type
|
const type = child.type
|
||||||
// check for dynamic text children
|
// check for dynamic text children
|
||||||
@ -158,65 +152,88 @@ export const transformElement: NodeTransform = (node, context) => {
|
|||||||
// pass directly if the only child is a text node
|
// pass directly if the only child is a text node
|
||||||
// (plain / interpolation / expression)
|
// (plain / interpolation / expression)
|
||||||
if (hasDynamicTextChild || type === NodeTypes.TEXT) {
|
if (hasDynamicTextChild || type === NodeTypes.TEXT) {
|
||||||
args.push(child)
|
vnodeChildren = child as TemplateTextChildNode
|
||||||
} else {
|
} else {
|
||||||
args.push(node.children)
|
vnodeChildren = node.children
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
args.push(node.children)
|
vnodeChildren = node.children
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// patchFlag & dynamicPropNames
|
// patchFlag & dynamicPropNames
|
||||||
if (patchFlag !== 0) {
|
if (patchFlag !== 0) {
|
||||||
if (!hasChildren) {
|
|
||||||
if (!hasProps) {
|
|
||||||
args.push(`null`)
|
|
||||||
}
|
|
||||||
args.push(`null`)
|
|
||||||
}
|
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
const flagNames = Object.keys(PatchFlagNames)
|
if (patchFlag < 0) {
|
||||||
.map(Number)
|
// special flags (negative and mutually exclusive)
|
||||||
.filter(n => n > 0 && patchFlag & n)
|
vnodePatchFlag = patchFlag + ` /* ${PatchFlagNames[patchFlag]} */`
|
||||||
.map(n => PatchFlagNames[n])
|
} else {
|
||||||
.join(`, `)
|
// bitwise flags
|
||||||
args.push(patchFlag + ` /* ${flagNames} */`)
|
const flagNames = Object.keys(PatchFlagNames)
|
||||||
|
.map(Number)
|
||||||
|
.filter(n => n > 0 && patchFlag & n)
|
||||||
|
.map(n => PatchFlagNames[n])
|
||||||
|
.join(`, `)
|
||||||
|
vnodePatchFlag = patchFlag + ` /* ${flagNames} */`
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
args.push(patchFlag + '')
|
vnodePatchFlag = String(patchFlag)
|
||||||
}
|
}
|
||||||
if (dynamicPropNames && dynamicPropNames.length) {
|
if (dynamicPropNames && dynamicPropNames.length) {
|
||||||
args.push(stringifyDynamicPropNames(dynamicPropNames))
|
vnodeDynamicProps = stringifyDynamicPropNames(dynamicPropNames)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const { loc } = node
|
node.codegenNode = createVNodeCall(
|
||||||
const vnode = createCallExpression(context.helper(CREATE_VNODE), args, loc)
|
context,
|
||||||
|
vnodeTag,
|
||||||
if (runtimeDirectives && runtimeDirectives.length) {
|
vnodeProps,
|
||||||
node.codegenNode = createCallExpression(
|
vnodeChildren,
|
||||||
context.helper(WITH_DIRECTIVES),
|
vnodePatchFlag,
|
||||||
[
|
vnodeDynamicProps,
|
||||||
vnode,
|
vnodeDirectives,
|
||||||
createArrayExpression(
|
!!shouldUseBlock,
|
||||||
runtimeDirectives.map(dir => buildDirectiveArgs(dir, context)),
|
false /* isForBlock */,
|
||||||
loc
|
node.loc
|
||||||
)
|
)
|
||||||
],
|
|
||||||
loc
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
node.codegenNode = vnode
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function stringifyDynamicPropNames(props: string[]): string {
|
export function resolveComponentType(
|
||||||
let propsNamesString = `[`
|
node: ComponentNode,
|
||||||
for (let i = 0, l = props.length; i < l; i++) {
|
context: TransformContext,
|
||||||
propsNamesString += JSON.stringify(props[i])
|
ssr = false
|
||||||
if (i < l - 1) propsNamesString += ', '
|
) {
|
||||||
|
const { tag } = node
|
||||||
|
|
||||||
|
// 1. dynamic component
|
||||||
|
const isProp =
|
||||||
|
node.tag === 'component' ? findProp(node, 'is') : findDir(node, 'is')
|
||||||
|
if (isProp) {
|
||||||
|
const exp =
|
||||||
|
isProp.type === NodeTypes.ATTRIBUTE
|
||||||
|
? isProp.value && createSimpleExpression(isProp.value.content, true)
|
||||||
|
: isProp.exp
|
||||||
|
if (exp) {
|
||||||
|
return createCallExpression(context.helper(RESOLVE_DYNAMIC_COMPONENT), [
|
||||||
|
exp
|
||||||
|
])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return propsNamesString + `]`
|
|
||||||
|
// 2. built-in components (Teleport, Transition, KeepAlive, Suspense...)
|
||||||
|
const builtIn = isCoreComponent(tag) || context.isBuiltInComponent(tag)
|
||||||
|
if (builtIn) {
|
||||||
|
// built-ins are simply fallthroughs / have special handling during ssr
|
||||||
|
// no we don't need to import their runtime equivalents
|
||||||
|
if (!ssr) context.helper(builtIn)
|
||||||
|
return builtIn
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. user component (resolve)
|
||||||
|
context.helper(RESOLVE_COMPONENT)
|
||||||
|
context.components.add(tag)
|
||||||
|
return toValidAssetId(tag, `component`)
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PropsExpression = ObjectExpression | CallExpression | ExpressionNode
|
export type PropsExpression = ObjectExpression | CallExpression | ExpressionNode
|
||||||
@ -224,14 +241,15 @@ export type PropsExpression = ObjectExpression | CallExpression | ExpressionNode
|
|||||||
export function buildProps(
|
export function buildProps(
|
||||||
node: ElementNode,
|
node: ElementNode,
|
||||||
context: TransformContext,
|
context: TransformContext,
|
||||||
props: ElementNode['props'] = node.props
|
props: ElementNode['props'] = node.props,
|
||||||
|
ssr = false
|
||||||
): {
|
): {
|
||||||
props: PropsExpression | undefined
|
props: PropsExpression | undefined
|
||||||
directives: DirectiveNode[]
|
directives: DirectiveNode[]
|
||||||
patchFlag: number
|
patchFlag: number
|
||||||
dynamicPropNames: string[]
|
dynamicPropNames: string[]
|
||||||
} {
|
} {
|
||||||
const elementLoc = node.loc
|
const { tag, loc: elementLoc } = node
|
||||||
const isComponent = node.tagType === ElementTypes.COMPONENT
|
const isComponent = node.tagType === ElementTypes.COMPONENT
|
||||||
let properties: ObjectExpression['properties'] = []
|
let properties: ObjectExpression['properties'] = []
|
||||||
const mergeArgs: PropsExpression[] = []
|
const mergeArgs: PropsExpression[] = []
|
||||||
@ -242,27 +260,40 @@ export function buildProps(
|
|||||||
let hasRef = false
|
let hasRef = false
|
||||||
let hasClassBinding = false
|
let hasClassBinding = false
|
||||||
let hasStyleBinding = false
|
let hasStyleBinding = false
|
||||||
|
let hasHydrationEventBinding = false
|
||||||
let hasDynamicKeys = false
|
let hasDynamicKeys = false
|
||||||
const dynamicPropNames: string[] = []
|
const dynamicPropNames: string[] = []
|
||||||
|
|
||||||
const analyzePatchFlag = ({ key, value }: Property) => {
|
const analyzePatchFlag = ({ key, value }: Property) => {
|
||||||
if (key.type === NodeTypes.SIMPLE_EXPRESSION && key.isStatic) {
|
if (key.type === NodeTypes.SIMPLE_EXPRESSION && key.isStatic) {
|
||||||
|
const name = key.content
|
||||||
|
if (
|
||||||
|
!isComponent &&
|
||||||
|
isOn(name) &&
|
||||||
|
// omit the flag for click handlers becaues hydration gives click
|
||||||
|
// dedicated fast path.
|
||||||
|
name.toLowerCase() !== 'onclick' &&
|
||||||
|
// omit v-model handlers
|
||||||
|
name !== 'onUpdate:modelValue'
|
||||||
|
) {
|
||||||
|
hasHydrationEventBinding = true
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
value.type === NodeTypes.JS_CACHE_EXPRESSION ||
|
value.type === NodeTypes.JS_CACHE_EXPRESSION ||
|
||||||
((value.type === NodeTypes.SIMPLE_EXPRESSION ||
|
((value.type === NodeTypes.SIMPLE_EXPRESSION ||
|
||||||
value.type === NodeTypes.COMPOUND_EXPRESSION) &&
|
value.type === NodeTypes.COMPOUND_EXPRESSION) &&
|
||||||
isStaticNode(value))
|
isStaticNode(value))
|
||||||
) {
|
) {
|
||||||
|
// skip if the prop is a cached handler or has constant value
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const name = key.content
|
|
||||||
if (name === 'ref') {
|
if (name === 'ref') {
|
||||||
hasRef = true
|
hasRef = true
|
||||||
} else if (name === 'class') {
|
} else if (name === 'class') {
|
||||||
hasClassBinding = true
|
hasClassBinding = true
|
||||||
} else if (name === 'style') {
|
} else if (name === 'style') {
|
||||||
hasStyleBinding = true
|
hasStyleBinding = true
|
||||||
} else if (name !== 'key') {
|
} else if (name !== 'key' && !dynamicPropNames.includes(name)) {
|
||||||
dynamicPropNames.push(name)
|
dynamicPropNames.push(name)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -278,6 +309,10 @@ export function buildProps(
|
|||||||
if (name === 'ref') {
|
if (name === 'ref') {
|
||||||
hasRef = true
|
hasRef = true
|
||||||
}
|
}
|
||||||
|
// skip :is on <component>
|
||||||
|
if (name === 'is' && tag === 'component') {
|
||||||
|
continue
|
||||||
|
}
|
||||||
properties.push(
|
properties.push(
|
||||||
createObjectProperty(
|
createObjectProperty(
|
||||||
createSimpleExpression(
|
createSimpleExpression(
|
||||||
@ -295,6 +330,8 @@ export function buildProps(
|
|||||||
} else {
|
} else {
|
||||||
// directives
|
// directives
|
||||||
const { name, arg, exp, loc } = prop
|
const { name, arg, exp, loc } = prop
|
||||||
|
const isBind = name === 'bind'
|
||||||
|
const isOn = name === 'on'
|
||||||
|
|
||||||
// skip v-slot - it is handled by its dedicated transform.
|
// skip v-slot - it is handled by its dedicated transform.
|
||||||
if (name === 'slot') {
|
if (name === 'slot') {
|
||||||
@ -305,15 +342,23 @@ export function buildProps(
|
|||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// skip v-once - it is handled by its dedicated transform.
|
// skip v-once - it is handled by its dedicated transform.
|
||||||
if (name === 'once') {
|
if (name === 'once') {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
// skip v-is and :is on <component>
|
||||||
|
if (
|
||||||
|
name === 'is' ||
|
||||||
|
(isBind && tag === 'component' && isBindKey(arg, 'is'))
|
||||||
|
) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// skip v-on in SSR compilation
|
||||||
|
if (isOn && ssr) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// special case for v-bind and v-on with no argument
|
// special case for v-bind and v-on with no argument
|
||||||
const isBind = name === 'bind'
|
|
||||||
const isOn = name === 'on'
|
|
||||||
if (!arg && (isBind || isOn)) {
|
if (!arg && (isBind || isOn)) {
|
||||||
hasDynamicKeys = true
|
hasDynamicKeys = true
|
||||||
if (exp) {
|
if (exp) {
|
||||||
@ -351,7 +396,7 @@ export function buildProps(
|
|||||||
if (directiveTransform) {
|
if (directiveTransform) {
|
||||||
// has built-in directive transform.
|
// has built-in directive transform.
|
||||||
const { props, needRuntime } = directiveTransform(prop, node, context)
|
const { props, needRuntime } = directiveTransform(prop, node, context)
|
||||||
props.forEach(analyzePatchFlag)
|
!ssr && props.forEach(analyzePatchFlag)
|
||||||
properties.push(...props)
|
properties.push(...props)
|
||||||
if (needRuntime) {
|
if (needRuntime) {
|
||||||
runtimeDirectives.push(prop)
|
runtimeDirectives.push(prop)
|
||||||
@ -405,8 +450,14 @@ export function buildProps(
|
|||||||
if (dynamicPropNames.length) {
|
if (dynamicPropNames.length) {
|
||||||
patchFlag |= PatchFlags.PROPS
|
patchFlag |= PatchFlags.PROPS
|
||||||
}
|
}
|
||||||
|
if (hasHydrationEventBinding) {
|
||||||
|
patchFlag |= PatchFlags.HYDRATE_EVENTS
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (patchFlag === 0 && (hasRef || runtimeDirectives.length > 0)) {
|
if (
|
||||||
|
(patchFlag === 0 || patchFlag === PatchFlags.HYDRATE_EVENTS) &&
|
||||||
|
(hasRef || runtimeDirectives.length > 0)
|
||||||
|
) {
|
||||||
patchFlag |= PatchFlags.NEED_PATCH
|
patchFlag |= PatchFlags.NEED_PATCH
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -437,12 +488,7 @@ function dedupeProperties(properties: Property[]): Property[] {
|
|||||||
const name = prop.key.content
|
const name = prop.key.content
|
||||||
const existing = knownProps.get(name)
|
const existing = knownProps.get(name)
|
||||||
if (existing) {
|
if (existing) {
|
||||||
if (
|
if (name === 'style' || name === 'class' || name.startsWith('on')) {
|
||||||
name === 'style' ||
|
|
||||||
name === 'class' ||
|
|
||||||
name.startsWith('on') ||
|
|
||||||
name.startsWith('vnode')
|
|
||||||
) {
|
|
||||||
mergeAsArray(existing, prop)
|
mergeAsArray(existing, prop)
|
||||||
}
|
}
|
||||||
// unexpected duplicate, should have emitted error during parse
|
// unexpected duplicate, should have emitted error during parse
|
||||||
@ -472,7 +518,6 @@ function buildDirectiveArgs(
|
|||||||
const dirArgs: ArrayExpression['elements'] = []
|
const dirArgs: ArrayExpression['elements'] = []
|
||||||
const runtime = directiveImportMap.get(dir)
|
const runtime = directiveImportMap.get(dir)
|
||||||
if (runtime) {
|
if (runtime) {
|
||||||
context.helper(runtime)
|
|
||||||
dirArgs.push(context.helperString(runtime))
|
dirArgs.push(context.helperString(runtime))
|
||||||
} else {
|
} else {
|
||||||
// inject statement for resolving directive
|
// inject statement for resolving directive
|
||||||
@ -507,3 +552,12 @@ function buildDirectiveArgs(
|
|||||||
}
|
}
|
||||||
return createArrayExpression(dirArgs, dir.loc)
|
return createArrayExpression(dirArgs, dir.loc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function stringifyDynamicPropNames(props: string[]): string {
|
||||||
|
let propsNamesString = `[`
|
||||||
|
for (let i = 0, l = props.length; i < l; i++) {
|
||||||
|
propsNamesString += JSON.stringify(props[i])
|
||||||
|
if (i < l - 1) propsNamesString += ', '
|
||||||
|
}
|
||||||
|
return propsNamesString + `]`
|
||||||
|
}
|
||||||
|
@ -16,7 +16,6 @@ import {
|
|||||||
CompoundExpressionNode,
|
CompoundExpressionNode,
|
||||||
createCompoundExpression
|
createCompoundExpression
|
||||||
} from '../ast'
|
} from '../ast'
|
||||||
import { Node, Function, Identifier, Property } from 'estree'
|
|
||||||
import {
|
import {
|
||||||
advancePositionWithClone,
|
advancePositionWithClone,
|
||||||
isSimpleIdentifier,
|
isSimpleIdentifier,
|
||||||
@ -25,6 +24,7 @@ import {
|
|||||||
} from '../utils'
|
} from '../utils'
|
||||||
import { isGloballyWhitelisted, makeMap } from '@vue/shared'
|
import { isGloballyWhitelisted, makeMap } from '@vue/shared'
|
||||||
import { createCompilerError, ErrorCodes } from '../errors'
|
import { createCompilerError, ErrorCodes } from '../errors'
|
||||||
|
import { Node, Function, Identifier, ObjectProperty } from '@babel/types'
|
||||||
|
|
||||||
const isLiteralWhitelisted = /*#__PURE__*/ makeMap('true,false,null,this')
|
const isLiteralWhitelisted = /*#__PURE__*/ makeMap('true,false,null,this')
|
||||||
|
|
||||||
@ -40,11 +40,15 @@ export const transformExpression: NodeTransform = (node, context) => {
|
|||||||
const dir = node.props[i]
|
const dir = node.props[i]
|
||||||
// do not process for v-on & v-for since they are special handled
|
// do not process for v-on & v-for since they are special handled
|
||||||
if (dir.type === NodeTypes.DIRECTIVE && dir.name !== 'for') {
|
if (dir.type === NodeTypes.DIRECTIVE && dir.name !== 'for') {
|
||||||
const exp = dir.exp as SimpleExpressionNode | undefined
|
const exp = dir.exp
|
||||||
const arg = dir.arg as SimpleExpressionNode | undefined
|
const arg = dir.arg
|
||||||
// do not process exp if this is v-on:arg - we need special handling
|
// do not process exp if this is v-on:arg - we need special handling
|
||||||
// for wrapping inline statements.
|
// for wrapping inline statements.
|
||||||
if (exp && !(dir.name === 'on' && arg)) {
|
if (
|
||||||
|
exp &&
|
||||||
|
exp.type === NodeTypes.SIMPLE_EXPRESSION &&
|
||||||
|
!(dir.name === 'on' && arg)
|
||||||
|
) {
|
||||||
dir.exp = processExpression(
|
dir.exp = processExpression(
|
||||||
exp,
|
exp,
|
||||||
context,
|
context,
|
||||||
@ -52,7 +56,7 @@ export const transformExpression: NodeTransform = (node, context) => {
|
|||||||
dir.name === 'slot'
|
dir.name === 'slot'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (arg && !arg.isStatic) {
|
if (arg && arg.type === NodeTypes.SIMPLE_EXPRESSION && !arg.isStatic) {
|
||||||
dir.arg = processExpression(arg, context)
|
dir.arg = processExpression(arg, context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -76,7 +80,9 @@ export function processExpression(
|
|||||||
context: TransformContext,
|
context: TransformContext,
|
||||||
// some expressions like v-slot props & v-for aliases should be parsed as
|
// some expressions like v-slot props & v-for aliases should be parsed as
|
||||||
// function params
|
// function params
|
||||||
asParams: boolean = false
|
asParams = false,
|
||||||
|
// v-on handler values may contain multiple statements
|
||||||
|
asRawStatements = false
|
||||||
): ExpressionNode {
|
): ExpressionNode {
|
||||||
if (!context.prefixIdentifiers || !node.content.trim()) {
|
if (!context.prefixIdentifiers || !node.content.trim()) {
|
||||||
return node
|
return node
|
||||||
@ -84,6 +90,8 @@ export function processExpression(
|
|||||||
|
|
||||||
// fast path if expression is a simple identifier.
|
// fast path if expression is a simple identifier.
|
||||||
const rawExp = node.content
|
const rawExp = node.content
|
||||||
|
// bail on parens to prevent any possible function invocations.
|
||||||
|
const bailConstant = rawExp.indexOf(`(`) > -1
|
||||||
if (isSimpleIdentifier(rawExp)) {
|
if (isSimpleIdentifier(rawExp)) {
|
||||||
if (
|
if (
|
||||||
!asParams &&
|
!asParams &&
|
||||||
@ -92,7 +100,7 @@ export function processExpression(
|
|||||||
!isLiteralWhitelisted(rawExp)
|
!isLiteralWhitelisted(rawExp)
|
||||||
) {
|
) {
|
||||||
node.content = `_ctx.${rawExp}`
|
node.content = `_ctx.${rawExp}`
|
||||||
} else if (!context.identifiers[rawExp]) {
|
} else if (!context.identifiers[rawExp] && !bailConstant) {
|
||||||
// mark node constant for hoisting unless it's referring a scope variable
|
// mark node constant for hoisting unless it's referring a scope variable
|
||||||
node.isConstant = true
|
node.isConstant = true
|
||||||
}
|
}
|
||||||
@ -100,26 +108,48 @@ export function processExpression(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let ast: any
|
let ast: any
|
||||||
// if the expression is supposed to be used in a function params position
|
// exp needs to be parsed differently:
|
||||||
// we need to parse it differently.
|
// 1. Multiple inline statements (v-on, with presence of `;`): parse as raw
|
||||||
const source = `(${rawExp})${asParams ? `=>{}` : ``}`
|
// exp, but make sure to pad with spaces for consistent ranges
|
||||||
|
// 2. Expressions: wrap with parens (for e.g. object expressions)
|
||||||
|
// 3. Function arguments (v-for, v-slot): place in a function argument position
|
||||||
|
const source = asRawStatements
|
||||||
|
? ` ${rawExp} `
|
||||||
|
: `(${rawExp})${asParams ? `=>{}` : ``}`
|
||||||
try {
|
try {
|
||||||
ast = parseJS(source, { ranges: true })
|
ast = parseJS(source, {
|
||||||
|
plugins: [
|
||||||
|
...context.expressionPlugins,
|
||||||
|
// by default we enable proposals slated for ES2020.
|
||||||
|
// full list at https://babeljs.io/docs/en/next/babel-parser#plugins
|
||||||
|
// this will need to be updated as the spec moves forward.
|
||||||
|
'bigInt',
|
||||||
|
'optionalChaining',
|
||||||
|
'nullishCoalescingOperator'
|
||||||
|
]
|
||||||
|
}).program
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
context.onError(
|
context.onError(
|
||||||
createCompilerError(ErrorCodes.X_INVALID_EXPRESSION, node.loc)
|
createCompilerError(
|
||||||
|
ErrorCodes.X_INVALID_EXPRESSION,
|
||||||
|
node.loc,
|
||||||
|
undefined,
|
||||||
|
e.message
|
||||||
|
)
|
||||||
)
|
)
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|
||||||
const ids: (Identifier & PrefixMeta)[] = []
|
const ids: (Identifier & PrefixMeta)[] = []
|
||||||
const knownIds = Object.create(context.identifiers)
|
const knownIds = Object.create(context.identifiers)
|
||||||
|
const isDuplicate = (node: Node & PrefixMeta): boolean =>
|
||||||
|
ids.some(id => id.start === node.start)
|
||||||
|
|
||||||
// walk the AST and look for identifiers that need to be prefixed with `_ctx.`.
|
// walk the AST and look for identifiers that need to be prefixed with `_ctx.`.
|
||||||
walkJS(ast, {
|
walkJS(ast, {
|
||||||
enter(node: Node & PrefixMeta, parent) {
|
enter(node: Node & PrefixMeta, parent) {
|
||||||
if (node.type === 'Identifier') {
|
if (node.type === 'Identifier') {
|
||||||
if (!ids.includes(node)) {
|
if (!isDuplicate(node)) {
|
||||||
const needPrefix = shouldPrefix(node, parent)
|
const needPrefix = shouldPrefix(node, parent)
|
||||||
if (!knownIds[node.name] && needPrefix) {
|
if (!knownIds[node.name] && needPrefix) {
|
||||||
if (isPropertyShorthand(node, parent)) {
|
if (isPropertyShorthand(node, parent)) {
|
||||||
@ -128,12 +158,13 @@ export function processExpression(
|
|||||||
node.prefix = `${node.name}: `
|
node.prefix = `${node.name}: `
|
||||||
}
|
}
|
||||||
node.name = `_ctx.${node.name}`
|
node.name = `_ctx.${node.name}`
|
||||||
node.isConstant = false
|
|
||||||
ids.push(node)
|
ids.push(node)
|
||||||
} else if (!isStaticPropertyKey(node, parent)) {
|
} else if (!isStaticPropertyKey(node, parent)) {
|
||||||
// The identifier is considered constant unless it's pointing to a
|
// The identifier is considered constant unless it's pointing to a
|
||||||
// scope variable (a v-for alias, or a v-slot prop)
|
// scope variable (a v-for alias, or a v-slot prop)
|
||||||
node.isConstant = !(needPrefix && knownIds[node.name])
|
if (!(needPrefix && knownIds[node.name]) && !bailConstant) {
|
||||||
|
node.isConstant = true
|
||||||
|
}
|
||||||
// also generate sub-expressions for other identifiers for better
|
// also generate sub-expressions for other identifiers for better
|
||||||
// source map support. (except for property keys which are static)
|
// source map support. (except for property keys which are static)
|
||||||
ids.push(node)
|
ids.push(node)
|
||||||
@ -223,7 +254,7 @@ export function processExpression(
|
|||||||
ret = createCompoundExpression(children, node.loc)
|
ret = createCompoundExpression(children, node.loc)
|
||||||
} else {
|
} else {
|
||||||
ret = node
|
ret = node
|
||||||
ret.isConstant = true
|
ret.isConstant = !bailConstant
|
||||||
}
|
}
|
||||||
ret.identifiers = Object.keys(knownIds)
|
ret.identifiers = Object.keys(knownIds)
|
||||||
return ret
|
return ret
|
||||||
@ -232,17 +263,21 @@ export function processExpression(
|
|||||||
const isFunction = (node: Node): node is Function =>
|
const isFunction = (node: Node): node is Function =>
|
||||||
/Function(Expression|Declaration)$/.test(node.type)
|
/Function(Expression|Declaration)$/.test(node.type)
|
||||||
|
|
||||||
const isPropertyKey = (node: Node, parent: Node) =>
|
const isStaticProperty = (node: Node): node is ObjectProperty =>
|
||||||
parent &&
|
node && node.type === 'ObjectProperty' && !node.computed
|
||||||
parent.type === 'Property' &&
|
|
||||||
parent.key === node &&
|
|
||||||
!parent.computed
|
|
||||||
|
|
||||||
const isPropertyShorthand = (node: Node, parent: Node) =>
|
const isPropertyShorthand = (node: Node, parent: Node) => {
|
||||||
isPropertyKey(node, parent) && (parent as Property).value === node
|
return (
|
||||||
|
isStaticProperty(parent) &&
|
||||||
|
parent.value === node &&
|
||||||
|
parent.key.type === 'Identifier' &&
|
||||||
|
parent.key.name === (node as Identifier).name &&
|
||||||
|
parent.key.start === node.start
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const isStaticPropertyKey = (node: Node, parent: Node) =>
|
const isStaticPropertyKey = (node: Node, parent: Node) =>
|
||||||
isPropertyKey(node, parent) && (parent as Property).value !== node
|
isStaticProperty(parent) && parent.key === node
|
||||||
|
|
||||||
function shouldPrefix(identifier: Identifier, parent: Node) {
|
function shouldPrefix(identifier: Identifier, parent: Node) {
|
||||||
if (
|
if (
|
||||||
@ -257,7 +292,8 @@ function shouldPrefix(identifier: Identifier, parent: Node) {
|
|||||||
!isStaticPropertyKey(identifier, parent) &&
|
!isStaticPropertyKey(identifier, parent) &&
|
||||||
// not a property of a MemberExpression
|
// not a property of a MemberExpression
|
||||||
!(
|
!(
|
||||||
parent.type === 'MemberExpression' &&
|
(parent.type === 'MemberExpression' ||
|
||||||
|
parent.type === 'OptionalMemberExpression') &&
|
||||||
parent.property === identifier &&
|
parent.property === identifier &&
|
||||||
!parent.computed
|
!parent.computed
|
||||||
) &&
|
) &&
|
||||||
|
@ -1,78 +1,32 @@
|
|||||||
import { NodeTransform } from '../transform'
|
import { NodeTransform, TransformContext } from '../transform'
|
||||||
import {
|
import {
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
CallExpression,
|
CallExpression,
|
||||||
createCallExpression,
|
createCallExpression,
|
||||||
ExpressionNode
|
ExpressionNode,
|
||||||
|
SlotOutletNode
|
||||||
} from '../ast'
|
} from '../ast'
|
||||||
import { isSlotOutlet } from '../utils'
|
import { isSlotOutlet, findProp } from '../utils'
|
||||||
import { buildProps } from './transformElement'
|
import { buildProps, PropsExpression } from './transformElement'
|
||||||
import { createCompilerError, ErrorCodes } from '../errors'
|
import { createCompilerError, ErrorCodes } from '../errors'
|
||||||
import { RENDER_SLOT } from '../runtimeHelpers'
|
import { RENDER_SLOT } from '../runtimeHelpers'
|
||||||
|
|
||||||
export const transformSlotOutlet: NodeTransform = (node, context) => {
|
export const transformSlotOutlet: NodeTransform = (node, context) => {
|
||||||
if (isSlotOutlet(node)) {
|
if (isSlotOutlet(node)) {
|
||||||
const { props, children, loc } = node
|
const { children, loc } = node
|
||||||
const $slots = context.prefixIdentifiers ? `_ctx.$slots` : `$slots`
|
const { slotName, slotProps } = processSlotOutlet(node, context)
|
||||||
let slotName: string | ExpressionNode = `"default"`
|
|
||||||
|
|
||||||
// check for <slot name="xxx" OR :name="xxx" />
|
const slotArgs: CallExpression['arguments'] = [
|
||||||
let nameIndex: number = -1
|
context.prefixIdentifiers ? `_ctx.$slots` : `$slots`,
|
||||||
for (let i = 0; i < props.length; i++) {
|
slotName
|
||||||
const prop = props[i]
|
]
|
||||||
if (prop.type === NodeTypes.ATTRIBUTE) {
|
|
||||||
if (prop.name === `name` && prop.value) {
|
|
||||||
// static name="xxx"
|
|
||||||
slotName = JSON.stringify(prop.value.content)
|
|
||||||
nameIndex = i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
} else if (prop.name === `bind`) {
|
|
||||||
const { arg, exp } = prop
|
|
||||||
if (
|
|
||||||
arg &&
|
|
||||||
exp &&
|
|
||||||
arg.type === NodeTypes.SIMPLE_EXPRESSION &&
|
|
||||||
arg.isStatic &&
|
|
||||||
arg.content === `name`
|
|
||||||
) {
|
|
||||||
// dynamic :name="xxx"
|
|
||||||
slotName = exp
|
|
||||||
nameIndex = i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const slotArgs: CallExpression['arguments'] = [$slots, slotName]
|
if (slotProps) {
|
||||||
const propsWithoutName =
|
slotArgs.push(slotProps)
|
||||||
nameIndex > -1
|
|
||||||
? props.slice(0, nameIndex).concat(props.slice(nameIndex + 1))
|
|
||||||
: props
|
|
||||||
let hasProps = propsWithoutName.length > 0
|
|
||||||
if (hasProps) {
|
|
||||||
const { props: propsExpression, directives } = buildProps(
|
|
||||||
node,
|
|
||||||
context,
|
|
||||||
propsWithoutName
|
|
||||||
)
|
|
||||||
if (directives.length) {
|
|
||||||
context.onError(
|
|
||||||
createCompilerError(
|
|
||||||
ErrorCodes.X_V_SLOT_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET,
|
|
||||||
directives[0].loc
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (propsExpression) {
|
|
||||||
slotArgs.push(propsExpression)
|
|
||||||
} else {
|
|
||||||
hasProps = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (children.length) {
|
if (children.length) {
|
||||||
if (!hasProps) {
|
if (!slotProps) {
|
||||||
slotArgs.push(`{}`)
|
slotArgs.push(`{}`)
|
||||||
}
|
}
|
||||||
slotArgs.push(children)
|
slotArgs.push(children)
|
||||||
@ -85,3 +39,49 @@ export const transformSlotOutlet: NodeTransform = (node, context) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface SlotOutletProcessResult {
|
||||||
|
slotName: string | ExpressionNode
|
||||||
|
slotProps: PropsExpression | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
export function processSlotOutlet(
|
||||||
|
node: SlotOutletNode,
|
||||||
|
context: TransformContext
|
||||||
|
): SlotOutletProcessResult {
|
||||||
|
let slotName: string | ExpressionNode = `"default"`
|
||||||
|
let slotProps: PropsExpression | undefined = undefined
|
||||||
|
|
||||||
|
// check for <slot name="xxx" OR :name="xxx" />
|
||||||
|
const name = findProp(node, 'name')
|
||||||
|
if (name) {
|
||||||
|
if (name.type === NodeTypes.ATTRIBUTE && name.value) {
|
||||||
|
// static name
|
||||||
|
slotName = JSON.stringify(name.value.content)
|
||||||
|
} else if (name.type === NodeTypes.DIRECTIVE && name.exp) {
|
||||||
|
// dynamic name
|
||||||
|
slotName = name.exp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const propsWithoutName = name
|
||||||
|
? node.props.filter(p => p !== name)
|
||||||
|
: node.props
|
||||||
|
if (propsWithoutName.length > 0) {
|
||||||
|
const { props, directives } = buildProps(node, context, propsWithoutName)
|
||||||
|
slotProps = props
|
||||||
|
if (directives.length) {
|
||||||
|
context.onError(
|
||||||
|
createCompilerError(
|
||||||
|
ErrorCodes.X_V_SLOT_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET,
|
||||||
|
directives[0].loc
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
slotName,
|
||||||
|
slotProps
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,29 +1,23 @@
|
|||||||
import { NodeTransform } from '../transform'
|
import { NodeTransform } from '../transform'
|
||||||
import {
|
import {
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
TemplateChildNode,
|
|
||||||
TextNode,
|
|
||||||
InterpolationNode,
|
|
||||||
CompoundExpressionNode,
|
CompoundExpressionNode,
|
||||||
createCallExpression,
|
createCallExpression,
|
||||||
CallExpression,
|
CallExpression,
|
||||||
ElementTypes
|
ElementTypes
|
||||||
} from '../ast'
|
} from '../ast'
|
||||||
|
import { isText } from '../utils'
|
||||||
import { CREATE_TEXT } from '../runtimeHelpers'
|
import { CREATE_TEXT } from '../runtimeHelpers'
|
||||||
import { PatchFlags, PatchFlagNames } from '@vue/shared'
|
import { PatchFlags, PatchFlagNames } from '@vue/shared'
|
||||||
|
|
||||||
const isText = (
|
|
||||||
node: TemplateChildNode
|
|
||||||
): node is TextNode | InterpolationNode =>
|
|
||||||
node.type === NodeTypes.INTERPOLATION || node.type === NodeTypes.TEXT
|
|
||||||
|
|
||||||
// Merge adjacent text nodes and expressions into a single expression
|
// Merge adjacent text nodes and expressions into a single expression
|
||||||
// e.g. <div>abc {{ d }} {{ e }}</div> should have a single expression node as child.
|
// e.g. <div>abc {{ d }} {{ e }}</div> should have a single expression node as child.
|
||||||
export const transformText: NodeTransform = (node, context) => {
|
export const transformText: NodeTransform = (node, context) => {
|
||||||
if (
|
if (
|
||||||
node.type === NodeTypes.ROOT ||
|
node.type === NodeTypes.ROOT ||
|
||||||
node.type === NodeTypes.ELEMENT ||
|
node.type === NodeTypes.ELEMENT ||
|
||||||
node.type === NodeTypes.FOR
|
node.type === NodeTypes.FOR ||
|
||||||
|
node.type === NodeTypes.IF_BRANCH
|
||||||
) {
|
) {
|
||||||
// perform the transform on node exit so that all expressions have already
|
// perform the transform on node exit so that all expressions have already
|
||||||
// been processed.
|
// been processed.
|
||||||
@ -84,7 +78,7 @@ export const transformText: NodeTransform = (node, context) => {
|
|||||||
callArgs.push(child)
|
callArgs.push(child)
|
||||||
}
|
}
|
||||||
// mark dynamic text with flag so it gets patched inside a block
|
// mark dynamic text with flag so it gets patched inside a block
|
||||||
if (child.type !== NodeTypes.TEXT) {
|
if (!context.ssr && child.type !== NodeTypes.TEXT) {
|
||||||
callArgs.push(
|
callArgs.push(
|
||||||
`${PatchFlags.TEXT} /* ${PatchFlagNames[PatchFlags.TEXT]} */`
|
`${PatchFlags.TEXT} /* ${PatchFlagNames[PatchFlags.TEXT]} */`
|
||||||
)
|
)
|
||||||
|
@ -30,7 +30,6 @@ export const transformBind: DirectiveTransform = (dir, node, context) => {
|
|||||||
return {
|
return {
|
||||||
props: [
|
props: [
|
||||||
createObjectProperty(arg!, exp || createSimpleExpression('', true, loc))
|
createObjectProperty(arg!, exp || createSimpleExpression('', true, loc))
|
||||||
],
|
]
|
||||||
needRuntime: false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,22 +8,28 @@ import {
|
|||||||
createSimpleExpression,
|
createSimpleExpression,
|
||||||
SourceLocation,
|
SourceLocation,
|
||||||
SimpleExpressionNode,
|
SimpleExpressionNode,
|
||||||
createSequenceExpression,
|
|
||||||
createCallExpression,
|
createCallExpression,
|
||||||
createFunctionExpression,
|
createFunctionExpression,
|
||||||
ElementTypes,
|
ElementTypes,
|
||||||
createObjectExpression,
|
createObjectExpression,
|
||||||
createObjectProperty,
|
createObjectProperty,
|
||||||
ForCodegenNode,
|
ForCodegenNode,
|
||||||
ElementCodegenNode,
|
RenderSlotCall,
|
||||||
SlotOutletCodegenNode,
|
SlotOutletNode,
|
||||||
SlotOutletNode
|
ElementNode,
|
||||||
|
DirectiveNode,
|
||||||
|
ForNode,
|
||||||
|
PlainElementNode,
|
||||||
|
createVNodeCall,
|
||||||
|
VNodeCall,
|
||||||
|
ForRenderListExpression,
|
||||||
|
BlockCodegenNode,
|
||||||
|
ForIteratorExpression
|
||||||
} from '../ast'
|
} from '../ast'
|
||||||
import { createCompilerError, ErrorCodes } from '../errors'
|
import { createCompilerError, ErrorCodes } from '../errors'
|
||||||
import {
|
import {
|
||||||
getInnerRange,
|
getInnerRange,
|
||||||
findProp,
|
findProp,
|
||||||
createBlockExpression,
|
|
||||||
isTemplateNode,
|
isTemplateNode,
|
||||||
isSlotOutlet,
|
isSlotOutlet,
|
||||||
injectProp
|
injectProp
|
||||||
@ -32,8 +38,7 @@ import {
|
|||||||
RENDER_LIST,
|
RENDER_LIST,
|
||||||
OPEN_BLOCK,
|
OPEN_BLOCK,
|
||||||
CREATE_BLOCK,
|
CREATE_BLOCK,
|
||||||
FRAGMENT,
|
FRAGMENT
|
||||||
WITH_DIRECTIVES
|
|
||||||
} from '../runtimeHelpers'
|
} from '../runtimeHelpers'
|
||||||
import { processExpression } from './transformExpression'
|
import { processExpression } from './transformExpression'
|
||||||
import { PatchFlags, PatchFlagNames } from '@vue/shared'
|
import { PatchFlags, PatchFlagNames } from '@vue/shared'
|
||||||
@ -41,141 +46,163 @@ import { PatchFlags, PatchFlagNames } from '@vue/shared'
|
|||||||
export const transformFor = createStructuralDirectiveTransform(
|
export const transformFor = createStructuralDirectiveTransform(
|
||||||
'for',
|
'for',
|
||||||
(node, dir, context) => {
|
(node, dir, context) => {
|
||||||
if (!dir.exp) {
|
const { helper } = context
|
||||||
context.onError(
|
return processFor(node, dir, context, forNode => {
|
||||||
createCompilerError(ErrorCodes.X_V_FOR_NO_EXPRESSION, dir.loc)
|
// create the loop render function expression now, and add the
|
||||||
)
|
// iterator on exit after all children have been traversed
|
||||||
return
|
const renderExp = createCallExpression(helper(RENDER_LIST), [
|
||||||
}
|
forNode.source
|
||||||
|
]) as ForRenderListExpression
|
||||||
const parseResult = parseForExpression(
|
const keyProp = findProp(node, `key`)
|
||||||
// can only be simple expression because vFor transform is applied
|
const fragmentFlag = keyProp
|
||||||
// before expression transform.
|
? PatchFlags.KEYED_FRAGMENT
|
||||||
dir.exp as SimpleExpressionNode,
|
: PatchFlags.UNKEYED_FRAGMENT
|
||||||
context
|
forNode.codegenNode = createVNodeCall(
|
||||||
)
|
context,
|
||||||
|
|
||||||
if (!parseResult) {
|
|
||||||
context.onError(
|
|
||||||
createCompilerError(ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION, dir.loc)
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const { helper, addIdentifiers, removeIdentifiers, scopes } = context
|
|
||||||
const { source, value, key, index } = parseResult
|
|
||||||
|
|
||||||
// create the loop render function expression now, and add the
|
|
||||||
// iterator on exit after all children have been traversed
|
|
||||||
const renderExp = createCallExpression(helper(RENDER_LIST), [source])
|
|
||||||
const keyProp = findProp(node, `key`)
|
|
||||||
const fragmentFlag = keyProp
|
|
||||||
? PatchFlags.KEYED_FRAGMENT
|
|
||||||
: PatchFlags.UNKEYED_FRAGMENT
|
|
||||||
const codegenNode = createSequenceExpression([
|
|
||||||
// fragment blocks disable tracking since they always diff their children
|
|
||||||
createCallExpression(helper(OPEN_BLOCK), [`false`]),
|
|
||||||
createCallExpression(helper(CREATE_BLOCK), [
|
|
||||||
helper(FRAGMENT),
|
helper(FRAGMENT),
|
||||||
`null`,
|
undefined,
|
||||||
renderExp,
|
renderExp,
|
||||||
`${fragmentFlag} /* ${PatchFlagNames[fragmentFlag]} */`
|
`${fragmentFlag} /* ${PatchFlagNames[fragmentFlag]} */`,
|
||||||
])
|
undefined,
|
||||||
]) as ForCodegenNode
|
undefined,
|
||||||
|
true /* isBlock */,
|
||||||
|
true /* isForBlock */,
|
||||||
|
node.loc
|
||||||
|
) as ForCodegenNode
|
||||||
|
|
||||||
context.replaceNode({
|
return () => {
|
||||||
type: NodeTypes.FOR,
|
// finish the codegen now that all children have been traversed
|
||||||
loc: dir.loc,
|
let childBlock: BlockCodegenNode
|
||||||
source,
|
const isTemplate = isTemplateNode(node)
|
||||||
valueAlias: value,
|
const { children } = forNode
|
||||||
keyAlias: key,
|
const needFragmentWrapper =
|
||||||
objectIndexAlias: index,
|
children.length > 1 || children[0].type !== NodeTypes.ELEMENT
|
||||||
children: node.tagType === ElementTypes.TEMPLATE ? node.children : [node],
|
const slotOutlet = isSlotOutlet(node)
|
||||||
codegenNode
|
? node
|
||||||
})
|
: isTemplate &&
|
||||||
|
node.children.length === 1 &&
|
||||||
// bookkeeping
|
isSlotOutlet(node.children[0])
|
||||||
scopes.vFor++
|
? (node.children[0] as SlotOutletNode) // api-extractor somehow fails to infer this
|
||||||
if (!__BROWSER__ && context.prefixIdentifiers) {
|
: null
|
||||||
// scope management
|
const keyProperty = keyProp
|
||||||
// inject identifiers to context
|
? createObjectProperty(
|
||||||
value && addIdentifiers(value)
|
`key`,
|
||||||
key && addIdentifiers(key)
|
keyProp.type === NodeTypes.ATTRIBUTE
|
||||||
index && addIdentifiers(index)
|
? createSimpleExpression(keyProp.value!.content, true)
|
||||||
}
|
: keyProp.exp!
|
||||||
|
)
|
||||||
return () => {
|
|
||||||
scopes.vFor--
|
|
||||||
if (!__BROWSER__ && context.prefixIdentifiers) {
|
|
||||||
value && removeIdentifiers(value)
|
|
||||||
key && removeIdentifiers(key)
|
|
||||||
index && removeIdentifiers(index)
|
|
||||||
}
|
|
||||||
|
|
||||||
// finish the codegen now that all children have been traversed
|
|
||||||
let childBlock
|
|
||||||
const isTemplate = isTemplateNode(node)
|
|
||||||
const slotOutlet = isSlotOutlet(node)
|
|
||||||
? node
|
|
||||||
: isTemplate &&
|
|
||||||
node.children.length === 1 &&
|
|
||||||
isSlotOutlet(node.children[0])
|
|
||||||
? (node.children[0] as SlotOutletNode) // api-extractor somehow fails to infer this
|
|
||||||
: null
|
: null
|
||||||
const keyProperty = keyProp
|
if (slotOutlet) {
|
||||||
? createObjectProperty(
|
// <slot v-for="..."> or <template v-for="..."><slot/></template>
|
||||||
`key`,
|
childBlock = slotOutlet.codegenNode as RenderSlotCall
|
||||||
keyProp.type === NodeTypes.ATTRIBUTE
|
if (isTemplate && keyProperty) {
|
||||||
? createSimpleExpression(keyProp.value!.content, true)
|
// <template v-for="..." :key="..."><slot/></template>
|
||||||
: keyProp.exp!
|
// we need to inject the key to the renderSlot() call.
|
||||||
)
|
// the props for renderSlot is passed as the 3rd argument.
|
||||||
: null
|
injectProp(childBlock, keyProperty, context)
|
||||||
if (slotOutlet) {
|
}
|
||||||
// <slot v-for="..."> or <template v-for="..."><slot/></template>
|
} else if (needFragmentWrapper) {
|
||||||
childBlock = slotOutlet.codegenNode as SlotOutletCodegenNode
|
// <template v-for="..."> with text or multi-elements
|
||||||
if (isTemplate && keyProperty) {
|
// should generate a fragment block for each loop
|
||||||
// <template v-for="..." :key="..."><slot/></template>
|
childBlock = createVNodeCall(
|
||||||
// we need to inject the key to the renderSlot() call.
|
context,
|
||||||
// the props for renderSlot is passed as the 3rd argument.
|
|
||||||
injectProp(childBlock, keyProperty, context)
|
|
||||||
}
|
|
||||||
} else if (isTemplate) {
|
|
||||||
// <template v-for="...">
|
|
||||||
// should generate a fragment block for each loop
|
|
||||||
childBlock = createBlockExpression(
|
|
||||||
createCallExpression(helper(CREATE_BLOCK), [
|
|
||||||
helper(FRAGMENT),
|
helper(FRAGMENT),
|
||||||
keyProperty ? createObjectExpression([keyProperty]) : `null`,
|
keyProperty ? createObjectExpression([keyProperty]) : undefined,
|
||||||
node.children,
|
node.children,
|
||||||
`${PatchFlags.STABLE_FRAGMENT} /* ${
|
`${PatchFlags.STABLE_FRAGMENT} /* ${
|
||||||
PatchFlagNames[PatchFlags.STABLE_FRAGMENT]
|
PatchFlagNames[PatchFlags.STABLE_FRAGMENT]
|
||||||
} */`
|
} */`,
|
||||||
]),
|
undefined,
|
||||||
context
|
undefined,
|
||||||
)
|
true
|
||||||
} else {
|
)
|
||||||
// Normal element v-for. Directly use the child's codegenNode
|
|
||||||
// arguments, but replace createVNode() with createBlock()
|
|
||||||
let codegenNode = node.codegenNode as ElementCodegenNode
|
|
||||||
if (codegenNode.callee === WITH_DIRECTIVES) {
|
|
||||||
codegenNode.arguments[0].callee = helper(CREATE_BLOCK)
|
|
||||||
} else {
|
} else {
|
||||||
codegenNode.callee = helper(CREATE_BLOCK)
|
// Normal element v-for. Directly use the child's codegenNode
|
||||||
|
// but mark it as a block.
|
||||||
|
childBlock = (children[0] as PlainElementNode)
|
||||||
|
.codegenNode as VNodeCall
|
||||||
|
childBlock.isBlock = true
|
||||||
|
helper(OPEN_BLOCK)
|
||||||
|
helper(CREATE_BLOCK)
|
||||||
}
|
}
|
||||||
childBlock = createBlockExpression(codegenNode, context)
|
|
||||||
}
|
|
||||||
|
|
||||||
renderExp.arguments.push(
|
renderExp.arguments.push(createFunctionExpression(
|
||||||
createFunctionExpression(
|
createForLoopParams(forNode.parseResult),
|
||||||
createForLoopParams(parseResult),
|
|
||||||
childBlock,
|
childBlock,
|
||||||
true /* force newline */
|
true /* force newline */
|
||||||
)
|
) as ForIteratorExpression)
|
||||||
)
|
}
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// target-agnostic transform used for both Client and SSR
|
||||||
|
export function processFor(
|
||||||
|
node: ElementNode,
|
||||||
|
dir: DirectiveNode,
|
||||||
|
context: TransformContext,
|
||||||
|
processCodegen?: (forNode: ForNode) => (() => void) | undefined
|
||||||
|
) {
|
||||||
|
if (!dir.exp) {
|
||||||
|
context.onError(
|
||||||
|
createCompilerError(ErrorCodes.X_V_FOR_NO_EXPRESSION, dir.loc)
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const parseResult = parseForExpression(
|
||||||
|
// can only be simple expression because vFor transform is applied
|
||||||
|
// before expression transform.
|
||||||
|
dir.exp as SimpleExpressionNode,
|
||||||
|
context
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!parseResult) {
|
||||||
|
context.onError(
|
||||||
|
createCompilerError(ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION, dir.loc)
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { addIdentifiers, removeIdentifiers, scopes } = context
|
||||||
|
const { source, value, key, index } = parseResult
|
||||||
|
|
||||||
|
const forNode: ForNode = {
|
||||||
|
type: NodeTypes.FOR,
|
||||||
|
loc: dir.loc,
|
||||||
|
source,
|
||||||
|
valueAlias: value,
|
||||||
|
keyAlias: key,
|
||||||
|
objectIndexAlias: index,
|
||||||
|
parseResult,
|
||||||
|
children: node.tagType === ElementTypes.TEMPLATE ? node.children : [node]
|
||||||
|
}
|
||||||
|
|
||||||
|
context.replaceNode(forNode)
|
||||||
|
|
||||||
|
// bookkeeping
|
||||||
|
scopes.vFor++
|
||||||
|
if (!__BROWSER__ && context.prefixIdentifiers) {
|
||||||
|
// scope management
|
||||||
|
// inject identifiers to context
|
||||||
|
value && addIdentifiers(value)
|
||||||
|
key && addIdentifiers(key)
|
||||||
|
index && addIdentifiers(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onExit = processCodegen && processCodegen(forNode)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
scopes.vFor--
|
||||||
|
if (!__BROWSER__ && context.prefixIdentifiers) {
|
||||||
|
value && removeIdentifiers(value)
|
||||||
|
key && removeIdentifiers(key)
|
||||||
|
index && removeIdentifiers(index)
|
||||||
|
}
|
||||||
|
if (onExit) onExit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/
|
const forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/
|
||||||
// This regex doesn't cover the case if key or index aliases have destructuring,
|
// This regex doesn't cover the case if key or index aliases have destructuring,
|
||||||
// but those do not make sense in the first place, so this works in practice.
|
// but those do not make sense in the first place, so this works in practice.
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
createStructuralDirectiveTransform,
|
createStructuralDirectiveTransform,
|
||||||
traverseChildren,
|
TransformContext,
|
||||||
TransformContext
|
traverseNode
|
||||||
} from '../transform'
|
} from '../transform'
|
||||||
import {
|
import {
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
@ -10,130 +10,139 @@ import {
|
|||||||
DirectiveNode,
|
DirectiveNode,
|
||||||
IfBranchNode,
|
IfBranchNode,
|
||||||
SimpleExpressionNode,
|
SimpleExpressionNode,
|
||||||
createSequenceExpression,
|
|
||||||
createCallExpression,
|
createCallExpression,
|
||||||
createConditionalExpression,
|
createConditionalExpression,
|
||||||
ConditionalExpression,
|
|
||||||
CallExpression,
|
|
||||||
createSimpleExpression,
|
createSimpleExpression,
|
||||||
createObjectProperty,
|
createObjectProperty,
|
||||||
createObjectExpression,
|
createObjectExpression,
|
||||||
IfCodegenNode,
|
|
||||||
IfConditionalExpression,
|
IfConditionalExpression,
|
||||||
BlockCodegenNode,
|
BlockCodegenNode,
|
||||||
SlotOutletCodegenNode,
|
IfNode,
|
||||||
ElementCodegenNode,
|
createVNodeCall
|
||||||
ComponentCodegenNode
|
|
||||||
} from '../ast'
|
} from '../ast'
|
||||||
import { createCompilerError, ErrorCodes } from '../errors'
|
import { createCompilerError, ErrorCodes } from '../errors'
|
||||||
import { processExpression } from './transformExpression'
|
import { processExpression } from './transformExpression'
|
||||||
import {
|
import {
|
||||||
OPEN_BLOCK,
|
|
||||||
CREATE_BLOCK,
|
CREATE_BLOCK,
|
||||||
FRAGMENT,
|
FRAGMENT,
|
||||||
WITH_DIRECTIVES,
|
CREATE_COMMENT,
|
||||||
CREATE_VNODE,
|
OPEN_BLOCK,
|
||||||
CREATE_COMMENT
|
TELEPORT
|
||||||
} from '../runtimeHelpers'
|
} from '../runtimeHelpers'
|
||||||
import { injectProp } from '../utils'
|
import { injectProp } from '../utils'
|
||||||
|
import { PatchFlags, PatchFlagNames } from '@vue/shared'
|
||||||
|
|
||||||
export const transformIf = createStructuralDirectiveTransform(
|
export const transformIf = createStructuralDirectiveTransform(
|
||||||
/^(if|else|else-if)$/,
|
/^(if|else|else-if)$/,
|
||||||
(node, dir, context) => {
|
(node, dir, context) => {
|
||||||
if (
|
return processIf(node, dir, context, (ifNode, branch, isRoot) => {
|
||||||
dir.name !== 'else' &&
|
|
||||||
(!dir.exp || !(dir.exp as SimpleExpressionNode).content.trim())
|
|
||||||
) {
|
|
||||||
const loc = dir.exp ? dir.exp.loc : node.loc
|
|
||||||
context.onError(
|
|
||||||
createCompilerError(ErrorCodes.X_V_IF_NO_EXPRESSION, dir.loc)
|
|
||||||
)
|
|
||||||
dir.exp = createSimpleExpression(`true`, false, loc)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!__BROWSER__ && context.prefixIdentifiers && dir.exp) {
|
|
||||||
// dir.exp can only be simple expression because vIf transform is applied
|
|
||||||
// before expression transform.
|
|
||||||
dir.exp = processExpression(dir.exp as SimpleExpressionNode, context)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dir.name === 'if') {
|
|
||||||
const branch = createIfBranch(node, dir)
|
|
||||||
const codegenNode = createSequenceExpression([
|
|
||||||
createCallExpression(context.helper(OPEN_BLOCK))
|
|
||||||
]) as IfCodegenNode
|
|
||||||
|
|
||||||
context.replaceNode({
|
|
||||||
type: NodeTypes.IF,
|
|
||||||
loc: node.loc,
|
|
||||||
branches: [branch],
|
|
||||||
codegenNode
|
|
||||||
})
|
|
||||||
|
|
||||||
// Exit callback. Complete the codegenNode when all children have been
|
// Exit callback. Complete the codegenNode when all children have been
|
||||||
// transformed.
|
// transformed.
|
||||||
return () => {
|
return () => {
|
||||||
codegenNode.expressions.push(createCodegenNodeForBranch(
|
if (isRoot) {
|
||||||
branch,
|
ifNode.codegenNode = createCodegenNodeForBranch(
|
||||||
0,
|
branch,
|
||||||
context
|
0,
|
||||||
) as IfConditionalExpression)
|
context
|
||||||
}
|
) as IfConditionalExpression
|
||||||
} else {
|
|
||||||
// locate the adjacent v-if
|
|
||||||
const siblings = context.parent!.children
|
|
||||||
const comments = []
|
|
||||||
let i = siblings.indexOf(node)
|
|
||||||
while (i-- >= -1) {
|
|
||||||
const sibling = siblings[i]
|
|
||||||
if (__DEV__ && sibling && sibling.type === NodeTypes.COMMENT) {
|
|
||||||
context.removeNode(sibling)
|
|
||||||
comments.unshift(sibling)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if (sibling && sibling.type === NodeTypes.IF) {
|
|
||||||
// move the node to the if node's branches
|
|
||||||
context.removeNode()
|
|
||||||
const branch = createIfBranch(node, dir)
|
|
||||||
if (__DEV__ && comments.length) {
|
|
||||||
branch.children = [...comments, ...branch.children]
|
|
||||||
}
|
|
||||||
sibling.branches.push(branch)
|
|
||||||
// since the branch was removed, it will not be traversed.
|
|
||||||
// make sure to traverse here.
|
|
||||||
traverseChildren(branch, context)
|
|
||||||
// make sure to reset currentNode after traversal to indicate this
|
|
||||||
// node has been removed.
|
|
||||||
context.currentNode = null
|
|
||||||
// attach this branch's codegen node to the v-if root.
|
|
||||||
let parentCondition = sibling.codegenNode
|
|
||||||
.expressions[1] as ConditionalExpression
|
|
||||||
while (true) {
|
|
||||||
if (
|
|
||||||
parentCondition.alternate.type ===
|
|
||||||
NodeTypes.JS_CONDITIONAL_EXPRESSION
|
|
||||||
) {
|
|
||||||
parentCondition = parentCondition.alternate
|
|
||||||
} else {
|
|
||||||
parentCondition.alternate = createCodegenNodeForBranch(
|
|
||||||
branch,
|
|
||||||
sibling.branches.length - 1,
|
|
||||||
context
|
|
||||||
)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
context.onError(
|
// attach this branch's codegen node to the v-if root.
|
||||||
createCompilerError(ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, node.loc)
|
let parentCondition = ifNode.codegenNode!
|
||||||
|
while (
|
||||||
|
parentCondition.alternate.type ===
|
||||||
|
NodeTypes.JS_CONDITIONAL_EXPRESSION
|
||||||
|
) {
|
||||||
|
parentCondition = parentCondition.alternate
|
||||||
|
}
|
||||||
|
parentCondition.alternate = createCodegenNodeForBranch(
|
||||||
|
branch,
|
||||||
|
ifNode.branches.length - 1,
|
||||||
|
context
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// target-agnostic transform used for both Client and SSR
|
||||||
|
export function processIf(
|
||||||
|
node: ElementNode,
|
||||||
|
dir: DirectiveNode,
|
||||||
|
context: TransformContext,
|
||||||
|
processCodegen?: (
|
||||||
|
node: IfNode,
|
||||||
|
branch: IfBranchNode,
|
||||||
|
isRoot: boolean
|
||||||
|
) => (() => void) | undefined
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
dir.name !== 'else' &&
|
||||||
|
(!dir.exp || !(dir.exp as SimpleExpressionNode).content.trim())
|
||||||
|
) {
|
||||||
|
const loc = dir.exp ? dir.exp.loc : node.loc
|
||||||
|
context.onError(
|
||||||
|
createCompilerError(ErrorCodes.X_V_IF_NO_EXPRESSION, dir.loc)
|
||||||
|
)
|
||||||
|
dir.exp = createSimpleExpression(`true`, false, loc)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!__BROWSER__ && context.prefixIdentifiers && dir.exp) {
|
||||||
|
// dir.exp can only be simple expression because vIf transform is applied
|
||||||
|
// before expression transform.
|
||||||
|
dir.exp = processExpression(dir.exp as SimpleExpressionNode, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dir.name === 'if') {
|
||||||
|
const branch = createIfBranch(node, dir)
|
||||||
|
const ifNode: IfNode = {
|
||||||
|
type: NodeTypes.IF,
|
||||||
|
loc: node.loc,
|
||||||
|
branches: [branch]
|
||||||
|
}
|
||||||
|
context.replaceNode(ifNode)
|
||||||
|
if (processCodegen) {
|
||||||
|
return processCodegen(ifNode, branch, true)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// locate the adjacent v-if
|
||||||
|
const siblings = context.parent!.children
|
||||||
|
const comments = []
|
||||||
|
let i = siblings.indexOf(node)
|
||||||
|
while (i-- >= -1) {
|
||||||
|
const sibling = siblings[i]
|
||||||
|
if (__DEV__ && sibling && sibling.type === NodeTypes.COMMENT) {
|
||||||
|
context.removeNode(sibling)
|
||||||
|
comments.unshift(sibling)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (sibling && sibling.type === NodeTypes.IF) {
|
||||||
|
// move the node to the if node's branches
|
||||||
|
context.removeNode()
|
||||||
|
const branch = createIfBranch(node, dir)
|
||||||
|
if (__DEV__ && comments.length) {
|
||||||
|
branch.children = [...comments, ...branch.children]
|
||||||
|
}
|
||||||
|
sibling.branches.push(branch)
|
||||||
|
const onExit = processCodegen && processCodegen(sibling, branch, false)
|
||||||
|
// since the branch was removed, it will not be traversed.
|
||||||
|
// make sure to traverse here.
|
||||||
|
traverseNode(branch, context)
|
||||||
|
// call on exit
|
||||||
|
if (onExit) onExit()
|
||||||
|
// make sure to reset currentNode after traversal to indicate this
|
||||||
|
// node has been removed.
|
||||||
|
context.currentNode = null
|
||||||
|
} else {
|
||||||
|
context.onError(
|
||||||
|
createCompilerError(ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, node.loc)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function createIfBranch(node: ElementNode, dir: DirectiveNode): IfBranchNode {
|
function createIfBranch(node: ElementNode, dir: DirectiveNode): IfBranchNode {
|
||||||
return {
|
return {
|
||||||
type: NodeTypes.IF_BRANCH,
|
type: NodeTypes.IF_BRANCH,
|
||||||
@ -160,7 +169,7 @@ function createCodegenNodeForBranch(
|
|||||||
])
|
])
|
||||||
) as IfConditionalExpression
|
) as IfConditionalExpression
|
||||||
} else {
|
} else {
|
||||||
return createChildrenCodegenNode(branch, index, context) as BlockCodegenNode
|
return createChildrenCodegenNode(branch, index, context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,46 +177,56 @@ function createChildrenCodegenNode(
|
|||||||
branch: IfBranchNode,
|
branch: IfBranchNode,
|
||||||
index: number,
|
index: number,
|
||||||
context: TransformContext
|
context: TransformContext
|
||||||
): CallExpression {
|
): BlockCodegenNode {
|
||||||
const { helper } = context
|
const { helper } = context
|
||||||
const keyProperty = createObjectProperty(
|
const keyProperty = createObjectProperty(
|
||||||
`key`,
|
`key`,
|
||||||
createSimpleExpression(index + '', false)
|
createSimpleExpression(index + '', false)
|
||||||
)
|
)
|
||||||
const { children } = branch
|
const { children } = branch
|
||||||
const child = children[0]
|
const firstChild = children[0]
|
||||||
const needFragmentWrapper =
|
const needFragmentWrapper =
|
||||||
children.length !== 1 || child.type !== NodeTypes.ELEMENT
|
children.length !== 1 || firstChild.type !== NodeTypes.ELEMENT
|
||||||
if (needFragmentWrapper) {
|
if (needFragmentWrapper) {
|
||||||
const blockArgs: CallExpression['arguments'] = [
|
if (children.length === 1 && firstChild.type === NodeTypes.FOR) {
|
||||||
helper(FRAGMENT),
|
|
||||||
createObjectExpression([keyProperty]),
|
|
||||||
children
|
|
||||||
]
|
|
||||||
if (children.length === 1 && child.type === NodeTypes.FOR) {
|
|
||||||
// optimize away nested fragments when child is a ForNode
|
// optimize away nested fragments when child is a ForNode
|
||||||
const forBlockArgs = child.codegenNode.expressions[1].arguments
|
const vnodeCall = firstChild.codegenNode!
|
||||||
// directly use the for block's children and patchFlag
|
injectProp(vnodeCall, keyProperty, context)
|
||||||
blockArgs[2] = forBlockArgs[2]
|
return vnodeCall
|
||||||
blockArgs[3] = forBlockArgs[3]
|
} else {
|
||||||
|
return createVNodeCall(
|
||||||
|
context,
|
||||||
|
helper(FRAGMENT),
|
||||||
|
createObjectExpression([keyProperty]),
|
||||||
|
children,
|
||||||
|
`${PatchFlags.STABLE_FRAGMENT} /* ${
|
||||||
|
PatchFlagNames[PatchFlags.STABLE_FRAGMENT]
|
||||||
|
} */`,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
branch.loc
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return createCallExpression(helper(CREATE_BLOCK), blockArgs)
|
|
||||||
} else {
|
} else {
|
||||||
const childCodegen = (child as ElementNode).codegenNode as
|
const vnodeCall = (firstChild as ElementNode)
|
||||||
| ElementCodegenNode
|
.codegenNode as BlockCodegenNode
|
||||||
| ComponentCodegenNode
|
|
||||||
| SlotOutletCodegenNode
|
|
||||||
let vnodeCall = childCodegen
|
|
||||||
// Element with custom directives. Locate the actual createVNode() call.
|
|
||||||
if (vnodeCall.callee === WITH_DIRECTIVES) {
|
|
||||||
vnodeCall = vnodeCall.arguments[0]
|
|
||||||
}
|
|
||||||
// Change createVNode to createBlock.
|
// Change createVNode to createBlock.
|
||||||
if (vnodeCall.callee === CREATE_VNODE) {
|
if (
|
||||||
vnodeCall.callee = helper(CREATE_BLOCK)
|
vnodeCall.type === NodeTypes.VNODE_CALL &&
|
||||||
|
// component vnodes are always tracked and its children are
|
||||||
|
// compiled into slots so no need to make it a block
|
||||||
|
((firstChild as ElementNode).tagType !== ElementTypes.COMPONENT ||
|
||||||
|
// teleport has component type but isn't always tracked
|
||||||
|
vnodeCall.tag === TELEPORT)
|
||||||
|
) {
|
||||||
|
vnodeCall.isBlock = true
|
||||||
|
helper(OPEN_BLOCK)
|
||||||
|
helper(CREATE_BLOCK)
|
||||||
}
|
}
|
||||||
// inject branch key
|
// inject branch key
|
||||||
injectProp(vnodeCall, keyProperty, context)
|
injectProp(vnodeCall, keyProperty, context)
|
||||||
return childCodegen
|
return vnodeCall
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,10 +44,7 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
|
|||||||
const eventName = arg
|
const eventName = arg
|
||||||
? arg.type === NodeTypes.SIMPLE_EXPRESSION && arg.isStatic
|
? arg.type === NodeTypes.SIMPLE_EXPRESSION && arg.isStatic
|
||||||
? `onUpdate:${arg.content}`
|
? `onUpdate:${arg.content}`
|
||||||
: createCompoundExpression([
|
: createCompoundExpression(['"onUpdate:" + ', arg])
|
||||||
'"onUpdate:" + ',
|
|
||||||
...(arg.type === NodeTypes.SIMPLE_EXPRESSION ? [arg] : arg.children)
|
|
||||||
])
|
|
||||||
: `onUpdate:modelValue`
|
: `onUpdate:modelValue`
|
||||||
|
|
||||||
const props = [
|
const props = [
|
||||||
@ -56,11 +53,7 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
|
|||||||
// "onUpdate:modelValue": $event => (foo = $event)
|
// "onUpdate:modelValue": $event => (foo = $event)
|
||||||
createObjectProperty(
|
createObjectProperty(
|
||||||
eventName,
|
eventName,
|
||||||
createCompoundExpression([
|
createCompoundExpression([`$event => (`, exp, ` = $event)`])
|
||||||
`$event => (`,
|
|
||||||
...(exp.type === NodeTypes.SIMPLE_EXPRESSION ? [exp] : exp.children),
|
|
||||||
` = $event)`
|
|
||||||
])
|
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -82,12 +75,7 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
|
|||||||
const modifiersKey = arg
|
const modifiersKey = arg
|
||||||
? arg.type === NodeTypes.SIMPLE_EXPRESSION && arg.isStatic
|
? arg.type === NodeTypes.SIMPLE_EXPRESSION && arg.isStatic
|
||||||
? `${arg.content}Modifiers`
|
? `${arg.content}Modifiers`
|
||||||
: createCompoundExpression([
|
: createCompoundExpression([arg, ' + "Modifiers"'])
|
||||||
...(arg.type === NodeTypes.SIMPLE_EXPRESSION
|
|
||||||
? [arg]
|
|
||||||
: arg.children),
|
|
||||||
' + "Modifiers"'
|
|
||||||
])
|
|
||||||
: `modelModifiers`
|
: `modelModifiers`
|
||||||
props.push(
|
props.push(
|
||||||
createObjectProperty(
|
createObjectProperty(
|
||||||
@ -101,5 +89,5 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createTransformProps(props: Property[] = []) {
|
function createTransformProps(props: Property[] = []) {
|
||||||
return { props, needRuntime: false }
|
return { props }
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ import {
|
|||||||
createCompoundExpression,
|
createCompoundExpression,
|
||||||
SimpleExpressionNode
|
SimpleExpressionNode
|
||||||
} from '../ast'
|
} from '../ast'
|
||||||
import { capitalize } from '@vue/shared'
|
import { capitalize, camelize } from '@vue/shared'
|
||||||
import { createCompilerError, ErrorCodes } from '../errors'
|
import { createCompilerError, ErrorCodes } from '../errors'
|
||||||
import { processExpression } from './transformExpression'
|
import { processExpression } from './transformExpression'
|
||||||
import { isMemberExpression, hasScopeRef } from '../utils'
|
import { isMemberExpression, hasScopeRef } from '../utils'
|
||||||
@ -26,23 +26,24 @@ export interface VOnDirectiveNode extends DirectiveNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const transformOn: DirectiveTransform = (
|
export const transformOn: DirectiveTransform = (
|
||||||
dir: VOnDirectiveNode,
|
dir,
|
||||||
node,
|
node,
|
||||||
context,
|
context,
|
||||||
augmentor
|
augmentor
|
||||||
) => {
|
) => {
|
||||||
const { loc, modifiers, arg } = dir
|
const { loc, modifiers, arg } = dir as VOnDirectiveNode
|
||||||
if (!dir.exp && !modifiers.length) {
|
if (!dir.exp && !modifiers.length) {
|
||||||
context.onError(createCompilerError(ErrorCodes.X_V_ON_NO_EXPRESSION, loc))
|
context.onError(createCompilerError(ErrorCodes.X_V_ON_NO_EXPRESSION, loc))
|
||||||
}
|
}
|
||||||
let eventName: ExpressionNode
|
let eventName: ExpressionNode
|
||||||
if (arg.type === NodeTypes.SIMPLE_EXPRESSION) {
|
if (arg.type === NodeTypes.SIMPLE_EXPRESSION) {
|
||||||
if (arg.isStatic) {
|
if (arg.isStatic) {
|
||||||
eventName = createSimpleExpression(
|
const rawName = arg.content
|
||||||
`on${capitalize(arg.content)}`,
|
// for @vnode-xxx event listeners, auto convert it to camelCase
|
||||||
true,
|
const normalizedName = rawName.startsWith(`vnode`)
|
||||||
arg.loc
|
? capitalize(camelize(rawName))
|
||||||
)
|
: capitalize(rawName)
|
||||||
|
eventName = createSimpleExpression(`on${normalizedName}`, true, arg.loc)
|
||||||
} else {
|
} else {
|
||||||
eventName = createCompoundExpression([`"on" + (`, arg, `)`])
|
eventName = createCompoundExpression([`"on" + (`, arg, `)`])
|
||||||
}
|
}
|
||||||
@ -54,16 +55,22 @@ export const transformOn: DirectiveTransform = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
// handler processing
|
// handler processing
|
||||||
let exp: ExpressionNode | undefined = dir.exp
|
let exp: ExpressionNode | undefined = dir.exp as
|
||||||
|
| SimpleExpressionNode
|
||||||
|
| undefined
|
||||||
|
if (exp && !exp.content.trim()) {
|
||||||
|
exp = undefined
|
||||||
|
}
|
||||||
let isCacheable: boolean = !exp
|
let isCacheable: boolean = !exp
|
||||||
if (exp) {
|
if (exp) {
|
||||||
const isMemberExp = isMemberExpression(exp.content)
|
const isMemberExp = isMemberExpression(exp.content)
|
||||||
const isInlineStatement = !(isMemberExp || fnExpRE.test(exp.content))
|
const isInlineStatement = !(isMemberExp || fnExpRE.test(exp.content))
|
||||||
|
const hasMultipleStatements = exp.content.includes(`;`)
|
||||||
|
|
||||||
// process the expression since it's been skipped
|
// process the expression since it's been skipped
|
||||||
if (!__BROWSER__ && context.prefixIdentifiers) {
|
if (!__BROWSER__ && context.prefixIdentifiers) {
|
||||||
context.addIdentifiers(`$event`)
|
context.addIdentifiers(`$event`)
|
||||||
exp = processExpression(exp, context)
|
exp = processExpression(exp, context, false, hasMultipleStatements)
|
||||||
context.removeIdentifiers(`$event`)
|
context.removeIdentifiers(`$event`)
|
||||||
// with scope analysis, the function is hoistable if it has no reference
|
// with scope analysis, the function is hoistable if it has no reference
|
||||||
// to scope variables.
|
// to scope variables.
|
||||||
@ -85,9 +92,9 @@ export const transformOn: DirectiveTransform = (
|
|||||||
if (isInlineStatement || (isCacheable && isMemberExp)) {
|
if (isInlineStatement || (isCacheable && isMemberExp)) {
|
||||||
// wrap inline statement in a function expression
|
// wrap inline statement in a function expression
|
||||||
exp = createCompoundExpression([
|
exp = createCompoundExpression([
|
||||||
`$event => (`,
|
`$event => ${hasMultipleStatements ? `{` : `(`}`,
|
||||||
...(exp.type === NodeTypes.SIMPLE_EXPRESSION ? [exp] : exp.children),
|
exp,
|
||||||
`)`
|
hasMultipleStatements ? `}` : `)`
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -98,8 +105,7 @@ export const transformOn: DirectiveTransform = (
|
|||||||
eventName,
|
eventName,
|
||||||
exp || createSimpleExpression(`() => {}`, false, loc)
|
exp || createSimpleExpression(`() => {}`, false, loc)
|
||||||
)
|
)
|
||||||
],
|
]
|
||||||
needRuntime: false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// apply extended compiler augmentor
|
// apply extended compiler augmentor
|
||||||
|
@ -19,12 +19,13 @@ import {
|
|||||||
FunctionExpression,
|
FunctionExpression,
|
||||||
CallExpression,
|
CallExpression,
|
||||||
createCallExpression,
|
createCallExpression,
|
||||||
createArrayExpression
|
createArrayExpression,
|
||||||
|
SlotsExpression
|
||||||
} from '../ast'
|
} from '../ast'
|
||||||
import { TransformContext, NodeTransform } from '../transform'
|
import { TransformContext, NodeTransform } from '../transform'
|
||||||
import { createCompilerError, ErrorCodes } from '../errors'
|
import { createCompilerError, ErrorCodes } from '../errors'
|
||||||
import { findDir, isTemplateNode, assert, isVSlot, hasScopeRef } from '../utils'
|
import { findDir, isTemplateNode, assert, isVSlot, hasScopeRef } from '../utils'
|
||||||
import { CREATE_SLOTS, RENDER_LIST } from '../runtimeHelpers'
|
import { CREATE_SLOTS, RENDER_LIST, WITH_CTX } from '../runtimeHelpers'
|
||||||
import { parseForExpression, createForLoopParams } from './vFor'
|
import { parseForExpression, createForLoopParams } from './vFor'
|
||||||
|
|
||||||
const isStaticExp = (p: JSChildNode): p is SimpleExpressionNode =>
|
const isStaticExp = (p: JSChildNode): p is SimpleExpressionNode =>
|
||||||
@ -93,19 +94,42 @@ export const trackVForSlotScopes: NodeTransform = (node, context) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type SlotFnBuilder = (
|
||||||
|
slotProps: ExpressionNode | undefined,
|
||||||
|
slotChildren: TemplateChildNode[],
|
||||||
|
loc: SourceLocation
|
||||||
|
) => FunctionExpression
|
||||||
|
|
||||||
|
const buildClientSlotFn: SlotFnBuilder = (props, children, loc) =>
|
||||||
|
createFunctionExpression(
|
||||||
|
props,
|
||||||
|
children,
|
||||||
|
false /* newline */,
|
||||||
|
true /* isSlot */,
|
||||||
|
children.length ? children[0].loc : loc
|
||||||
|
)
|
||||||
|
|
||||||
// Instead of being a DirectiveTransform, v-slot processing is called during
|
// Instead of being a DirectiveTransform, v-slot processing is called during
|
||||||
// transformElement to build the slots object for a component.
|
// transformElement to build the slots object for a component.
|
||||||
export function buildSlots(
|
export function buildSlots(
|
||||||
node: ElementNode,
|
node: ElementNode,
|
||||||
context: TransformContext
|
context: TransformContext,
|
||||||
|
buildSlotFn: SlotFnBuilder = buildClientSlotFn
|
||||||
): {
|
): {
|
||||||
slots: ObjectExpression | CallExpression
|
slots: SlotsExpression
|
||||||
hasDynamicSlots: boolean
|
hasDynamicSlots: boolean
|
||||||
} {
|
} {
|
||||||
|
context.helper(WITH_CTX)
|
||||||
|
|
||||||
const { children, loc } = node
|
const { children, loc } = node
|
||||||
const slotsProperties: Property[] = []
|
const slotsProperties: Property[] = []
|
||||||
const dynamicSlots: (ConditionalExpression | CallExpression)[] = []
|
const dynamicSlots: (ConditionalExpression | CallExpression)[] = []
|
||||||
|
|
||||||
|
const buildDefaultSlotProperty = (
|
||||||
|
props: ExpressionNode | undefined,
|
||||||
|
children: TemplateChildNode[]
|
||||||
|
) => createObjectProperty(`default`, buildSlotFn(props, children, loc))
|
||||||
|
|
||||||
// If the slot is inside a v-for or another v-slot, force it to be dynamic
|
// If the slot is inside a v-for or another v-slot, force it to be dynamic
|
||||||
// since it likely uses a scope variable.
|
// since it likely uses a scope variable.
|
||||||
let hasDynamicSlots = context.scopes.vSlot > 0 || context.scopes.vFor > 0
|
let hasDynamicSlots = context.scopes.vSlot > 0 || context.scopes.vFor > 0
|
||||||
@ -115,24 +139,26 @@ export function buildSlots(
|
|||||||
hasDynamicSlots = hasScopeRef(node, context.identifiers)
|
hasDynamicSlots = hasScopeRef(node, context.identifiers)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. Check for default slot with slotProps on component itself.
|
// 1. Check for slot with slotProps on component itself.
|
||||||
// <Comp v-slot="{ prop }"/>
|
// <Comp v-slot="{ prop }"/>
|
||||||
const explicitDefaultSlot = findDir(node, 'slot', true)
|
const onComponentSlot = findDir(node, 'slot', true)
|
||||||
if (explicitDefaultSlot) {
|
if (onComponentSlot) {
|
||||||
const { arg, exp, loc } = explicitDefaultSlot
|
const { arg, exp } = onComponentSlot
|
||||||
if (arg) {
|
slotsProperties.push(
|
||||||
context.onError(
|
createObjectProperty(
|
||||||
createCompilerError(ErrorCodes.X_V_SLOT_NAMED_SLOT_ON_COMPONENT, loc)
|
arg || createSimpleExpression('default', true),
|
||||||
|
buildSlotFn(exp, children, loc)
|
||||||
)
|
)
|
||||||
}
|
)
|
||||||
slotsProperties.push(buildDefaultSlot(exp, children, loc))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Iterate through children and check for template slots
|
// 2. Iterate through children and check for template slots
|
||||||
// <template v-slot:foo="{ prop }">
|
// <template v-slot:foo="{ prop }">
|
||||||
let hasTemplateSlots = false
|
let hasTemplateSlots = false
|
||||||
let extraneousChild: TemplateChildNode | undefined = undefined
|
let hasNamedDefaultSlot = false
|
||||||
|
const implicitDefaultChildren: TemplateChildNode[] = []
|
||||||
const seenSlotNames = new Set<string>()
|
const seenSlotNames = new Set<string>()
|
||||||
|
|
||||||
for (let i = 0; i < children.length; i++) {
|
for (let i = 0; i < children.length; i++) {
|
||||||
const slotElement = children[i]
|
const slotElement = children[i]
|
||||||
let slotDir
|
let slotDir
|
||||||
@ -142,14 +168,14 @@ export function buildSlots(
|
|||||||
!(slotDir = findDir(slotElement, 'slot', true))
|
!(slotDir = findDir(slotElement, 'slot', true))
|
||||||
) {
|
) {
|
||||||
// not a <template v-slot>, skip.
|
// not a <template v-slot>, skip.
|
||||||
if (slotElement.type !== NodeTypes.COMMENT && !extraneousChild) {
|
if (slotElement.type !== NodeTypes.COMMENT) {
|
||||||
extraneousChild = slotElement
|
implicitDefaultChildren.push(slotElement)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if (explicitDefaultSlot) {
|
if (onComponentSlot) {
|
||||||
// already has on-component default slot - this is incorrect usage.
|
// already has on-component slot - this is incorrect usage.
|
||||||
context.onError(
|
context.onError(
|
||||||
createCompilerError(ErrorCodes.X_V_SLOT_MIXED_SLOT_USAGE, slotDir.loc)
|
createCompilerError(ErrorCodes.X_V_SLOT_MIXED_SLOT_USAGE, slotDir.loc)
|
||||||
)
|
)
|
||||||
@ -172,13 +198,7 @@ export function buildSlots(
|
|||||||
hasDynamicSlots = true
|
hasDynamicSlots = true
|
||||||
}
|
}
|
||||||
|
|
||||||
const slotFunction = createFunctionExpression(
|
const slotFunction = buildSlotFn(slotProps, slotChildren, slotLoc)
|
||||||
slotProps,
|
|
||||||
slotChildren,
|
|
||||||
false,
|
|
||||||
slotChildren.length ? slotChildren[0].loc : slotLoc
|
|
||||||
)
|
|
||||||
|
|
||||||
// check if this slot is conditional (v-if/v-for)
|
// check if this slot is conditional (v-if/v-for)
|
||||||
let vIf: DirectiveNode | undefined
|
let vIf: DirectiveNode | undefined
|
||||||
let vElse: DirectiveNode | undefined
|
let vElse: DirectiveNode | undefined
|
||||||
@ -244,7 +264,7 @@ export function buildSlots(
|
|||||||
createFunctionExpression(
|
createFunctionExpression(
|
||||||
createForLoopParams(parseResult),
|
createForLoopParams(parseResult),
|
||||||
buildDynamicSlot(slotName, slotFunction),
|
buildDynamicSlot(slotName, slotFunction),
|
||||||
true
|
true /* force newline */
|
||||||
)
|
)
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
@ -266,36 +286,46 @@ export function buildSlots(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
seenSlotNames.add(staticSlotName)
|
seenSlotNames.add(staticSlotName)
|
||||||
|
if (staticSlotName === 'default') {
|
||||||
|
hasNamedDefaultSlot = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
slotsProperties.push(createObjectProperty(slotName, slotFunction))
|
slotsProperties.push(createObjectProperty(slotName, slotFunction))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasTemplateSlots && extraneousChild) {
|
if (!onComponentSlot) {
|
||||||
context.onError(
|
if (!hasTemplateSlots) {
|
||||||
createCompilerError(
|
// implicit default slot (on component)
|
||||||
ErrorCodes.X_V_SLOT_EXTRANEOUS_NON_SLOT_CHILDREN,
|
slotsProperties.push(buildDefaultSlotProperty(undefined, children))
|
||||||
extraneousChild.loc
|
} else if (implicitDefaultChildren.length) {
|
||||||
)
|
// implicit default slot (mixed with named slots)
|
||||||
)
|
if (hasNamedDefaultSlot) {
|
||||||
|
context.onError(
|
||||||
|
createCompilerError(
|
||||||
|
ErrorCodes.X_V_SLOT_EXTRANEOUS_DEFAULT_SLOT_CHILDREN,
|
||||||
|
implicitDefaultChildren[0].loc
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
slotsProperties.push(
|
||||||
|
buildDefaultSlotProperty(undefined, implicitDefaultChildren)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!explicitDefaultSlot && !hasTemplateSlots) {
|
let slots = createObjectExpression(
|
||||||
// implicit default slot.
|
|
||||||
slotsProperties.push(buildDefaultSlot(undefined, children, loc))
|
|
||||||
}
|
|
||||||
|
|
||||||
let slots: ObjectExpression | CallExpression = createObjectExpression(
|
|
||||||
slotsProperties.concat(
|
slotsProperties.concat(
|
||||||
createObjectProperty(`_compiled`, createSimpleExpression(`true`, false))
|
createObjectProperty(`_`, createSimpleExpression(`1`, false))
|
||||||
),
|
),
|
||||||
loc
|
loc
|
||||||
)
|
) as SlotsExpression
|
||||||
if (dynamicSlots.length) {
|
if (dynamicSlots.length) {
|
||||||
slots = createCallExpression(context.helper(CREATE_SLOTS), [
|
slots = createCallExpression(context.helper(CREATE_SLOTS), [
|
||||||
slots,
|
slots,
|
||||||
createArrayExpression(dynamicSlots)
|
createArrayExpression(dynamicSlots)
|
||||||
])
|
]) as SlotsExpression
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -304,22 +334,6 @@ export function buildSlots(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildDefaultSlot(
|
|
||||||
slotProps: ExpressionNode | undefined,
|
|
||||||
children: TemplateChildNode[],
|
|
||||||
loc: SourceLocation
|
|
||||||
): Property {
|
|
||||||
return createObjectProperty(
|
|
||||||
`default`,
|
|
||||||
createFunctionExpression(
|
|
||||||
slotProps,
|
|
||||||
children,
|
|
||||||
false,
|
|
||||||
children.length ? children[0].loc : loc
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildDynamicSlot(
|
function buildDynamicSlot(
|
||||||
name: ExpressionNode,
|
name: ExpressionNode,
|
||||||
fn: FunctionExpression
|
fn: FunctionExpression
|
||||||
|
@ -4,8 +4,6 @@ import {
|
|||||||
ElementNode,
|
ElementNode,
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
CallExpression,
|
CallExpression,
|
||||||
SequenceExpression,
|
|
||||||
createSequenceExpression,
|
|
||||||
createCallExpression,
|
createCallExpression,
|
||||||
DirectiveNode,
|
DirectiveNode,
|
||||||
ElementTypes,
|
ElementTypes,
|
||||||
@ -17,33 +15,31 @@ import {
|
|||||||
createObjectExpression,
|
createObjectExpression,
|
||||||
SlotOutletNode,
|
SlotOutletNode,
|
||||||
TemplateNode,
|
TemplateNode,
|
||||||
BlockCodegenNode,
|
RenderSlotCall,
|
||||||
ElementCodegenNode,
|
|
||||||
SlotOutletCodegenNode,
|
|
||||||
ComponentCodegenNode,
|
|
||||||
ExpressionNode,
|
ExpressionNode,
|
||||||
IfBranchNode
|
IfBranchNode,
|
||||||
|
TextNode,
|
||||||
|
InterpolationNode,
|
||||||
|
VNodeCall
|
||||||
} from './ast'
|
} from './ast'
|
||||||
import { parse } from 'acorn'
|
|
||||||
import { walk } from 'estree-walker'
|
|
||||||
import { TransformContext } from './transform'
|
import { TransformContext } from './transform'
|
||||||
import {
|
import {
|
||||||
OPEN_BLOCK,
|
|
||||||
MERGE_PROPS,
|
MERGE_PROPS,
|
||||||
RENDER_SLOT,
|
TELEPORT,
|
||||||
PORTAL,
|
|
||||||
SUSPENSE,
|
SUSPENSE,
|
||||||
KEEP_ALIVE,
|
KEEP_ALIVE,
|
||||||
BASE_TRANSITION
|
BASE_TRANSITION
|
||||||
} from './runtimeHelpers'
|
} from './runtimeHelpers'
|
||||||
import { isString, isFunction, isObject, hyphenate } from '@vue/shared'
|
import { isString, isFunction, isObject, hyphenate } from '@vue/shared'
|
||||||
|
import { parse } from '@babel/parser'
|
||||||
|
import { Node } from '@babel/types'
|
||||||
|
|
||||||
export const isBuiltInType = (tag: string, expected: string): boolean =>
|
export const isBuiltInType = (tag: string, expected: string): boolean =>
|
||||||
tag === expected || tag === hyphenate(expected)
|
tag === expected || tag === hyphenate(expected)
|
||||||
|
|
||||||
export function isCoreComponent(tag: string): symbol | void {
|
export function isCoreComponent(tag: string): symbol | void {
|
||||||
if (isBuiltInType(tag, 'Portal')) {
|
if (isBuiltInType(tag, 'Teleport')) {
|
||||||
return PORTAL
|
return TELEPORT
|
||||||
} else if (isBuiltInType(tag, 'Suspense')) {
|
} else if (isBuiltInType(tag, 'Suspense')) {
|
||||||
return SUSPENSE
|
return SUSPENSE
|
||||||
} else if (isBuiltInType(tag, 'KeepAlive')) {
|
} else if (isBuiltInType(tag, 'KeepAlive')) {
|
||||||
@ -57,10 +53,10 @@ export function isCoreComponent(tag: string): symbol | void {
|
|||||||
// lazy require dependencies so that they don't end up in rollup's dep graph
|
// lazy require dependencies so that they don't end up in rollup's dep graph
|
||||||
// and thus can be tree-shaken in browser builds.
|
// and thus can be tree-shaken in browser builds.
|
||||||
let _parse: typeof parse
|
let _parse: typeof parse
|
||||||
let _walk: typeof walk
|
let _walk: any
|
||||||
|
|
||||||
export function loadDep(name: string) {
|
export function loadDep(name: string) {
|
||||||
if (typeof process !== 'undefined' && isFunction(require)) {
|
if (!__BROWSER__ && typeof process !== 'undefined' && isFunction(require)) {
|
||||||
return require(name)
|
return require(name)
|
||||||
} else {
|
} else {
|
||||||
// This is only used when we are building a dev-only build of the compiler
|
// This is only used when we are building a dev-only build of the compiler
|
||||||
@ -74,11 +70,18 @@ export const parseJS: typeof parse = (code, options) => {
|
|||||||
!__BROWSER__,
|
!__BROWSER__,
|
||||||
`Expression AST analysis can only be performed in non-browser builds.`
|
`Expression AST analysis can only be performed in non-browser builds.`
|
||||||
)
|
)
|
||||||
const parse = _parse || (_parse = loadDep('acorn').parse)
|
if (!_parse) {
|
||||||
return parse(code, options)
|
_parse = loadDep('@babel/parser').parse
|
||||||
|
}
|
||||||
|
return _parse(code, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const walkJS: typeof walk = (ast, walker) => {
|
interface Walker {
|
||||||
|
enter?(node: Node, parent: Node): void
|
||||||
|
leave?(node: Node): void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const walkJS = (ast: Node, walker: Walker) => {
|
||||||
assert(
|
assert(
|
||||||
!__BROWSER__,
|
!__BROWSER__,
|
||||||
`Expression AST analysis can only be performed in non-browser builds.`
|
`Expression AST analysis can only be performed in non-browser builds.`
|
||||||
@ -149,7 +152,7 @@ export function advancePositionWithMutation(
|
|||||||
pos.column =
|
pos.column =
|
||||||
lastNewLinePos === -1
|
lastNewLinePos === -1
|
||||||
? pos.column + numberOfCharacters
|
? pos.column + numberOfCharacters
|
||||||
: Math.max(1, numberOfCharacters - lastNewLinePos)
|
: numberOfCharacters - lastNewLinePos
|
||||||
|
|
||||||
return pos
|
return pos
|
||||||
}
|
}
|
||||||
@ -181,36 +184,46 @@ export function findDir(
|
|||||||
export function findProp(
|
export function findProp(
|
||||||
node: ElementNode,
|
node: ElementNode,
|
||||||
name: string,
|
name: string,
|
||||||
dynamicOnly: boolean = false
|
dynamicOnly: boolean = false,
|
||||||
|
allowEmpty: boolean = false
|
||||||
): ElementNode['props'][0] | undefined {
|
): ElementNode['props'][0] | undefined {
|
||||||
for (let i = 0; i < node.props.length; i++) {
|
for (let i = 0; i < node.props.length; i++) {
|
||||||
const p = node.props[i]
|
const p = node.props[i]
|
||||||
if (p.type === NodeTypes.ATTRIBUTE) {
|
if (p.type === NodeTypes.ATTRIBUTE) {
|
||||||
if (dynamicOnly) continue
|
if (dynamicOnly) continue
|
||||||
if (p.name === name && p.value) {
|
if (p.name === name && (p.value || allowEmpty)) {
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
} else if (
|
} else if (p.name === 'bind' && p.exp && isBindKey(p.arg, name)) {
|
||||||
p.name === 'bind' &&
|
|
||||||
p.arg &&
|
|
||||||
p.arg.type === NodeTypes.SIMPLE_EXPRESSION &&
|
|
||||||
p.arg.isStatic &&
|
|
||||||
p.arg.content === name &&
|
|
||||||
p.exp
|
|
||||||
) {
|
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createBlockExpression(
|
export function isBindKey(arg: DirectiveNode['arg'], name: string): boolean {
|
||||||
blockExp: BlockCodegenNode,
|
return !!(
|
||||||
context: TransformContext
|
arg &&
|
||||||
): SequenceExpression {
|
arg.type === NodeTypes.SIMPLE_EXPRESSION &&
|
||||||
return createSequenceExpression([
|
arg.isStatic &&
|
||||||
createCallExpression(context.helper(OPEN_BLOCK)),
|
arg.content === name
|
||||||
blockExp
|
)
|
||||||
])
|
}
|
||||||
|
|
||||||
|
export function hasDynamicKeyVBind(node: ElementNode): boolean {
|
||||||
|
return node.props.some(
|
||||||
|
p =>
|
||||||
|
p.type === NodeTypes.DIRECTIVE &&
|
||||||
|
p.name === 'bind' &&
|
||||||
|
(!p.arg || // v-bind="obj"
|
||||||
|
p.arg.type !== NodeTypes.SIMPLE_EXPRESSION || // v-bind:[_ctx.foo]
|
||||||
|
!p.arg.isStatic) // v-bind:[foo]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isText(
|
||||||
|
node: TemplateChildNode
|
||||||
|
): node is TextNode | InterpolationNode {
|
||||||
|
return node.type === NodeTypes.INTERPOLATION || node.type === NodeTypes.TEXT
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isVSlot(p: ElementNode['props'][0]): p is DirectiveNode {
|
export function isVSlot(p: ElementNode['props'][0]): p is DirectiveNode {
|
||||||
@ -232,13 +245,13 @@ export function isSlotOutlet(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function injectProp(
|
export function injectProp(
|
||||||
node: ElementCodegenNode | ComponentCodegenNode | SlotOutletCodegenNode,
|
node: VNodeCall | RenderSlotCall,
|
||||||
prop: Property,
|
prop: Property,
|
||||||
context: TransformContext
|
context: TransformContext
|
||||||
) {
|
) {
|
||||||
let propsWithInjection: ObjectExpression | CallExpression
|
let propsWithInjection: ObjectExpression | CallExpression
|
||||||
const props =
|
const props =
|
||||||
node.callee === RENDER_SLOT ? node.arguments[2] : node.arguments[1]
|
node.type === NodeTypes.VNODE_CALL ? node.props : node.arguments[2]
|
||||||
if (props == null || isString(props)) {
|
if (props == null || isString(props)) {
|
||||||
propsWithInjection = createObjectExpression([prop])
|
propsWithInjection = createObjectExpression([prop])
|
||||||
} else if (props.type === NodeTypes.JS_CALL_EXPRESSION) {
|
} else if (props.type === NodeTypes.JS_CALL_EXPRESSION) {
|
||||||
@ -253,7 +266,19 @@ export function injectProp(
|
|||||||
}
|
}
|
||||||
propsWithInjection = props
|
propsWithInjection = props
|
||||||
} else if (props.type === NodeTypes.JS_OBJECT_EXPRESSION) {
|
} else if (props.type === NodeTypes.JS_OBJECT_EXPRESSION) {
|
||||||
props.properties.unshift(prop)
|
let alreadyExists = false
|
||||||
|
// check existing key to avoid overriding user provided keys
|
||||||
|
if (prop.key.type === NodeTypes.SIMPLE_EXPRESSION) {
|
||||||
|
const propKeyName = prop.key.content
|
||||||
|
alreadyExists = props.properties.some(
|
||||||
|
p =>
|
||||||
|
p.key.type === NodeTypes.SIMPLE_EXPRESSION &&
|
||||||
|
p.key.content === propKeyName
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (!alreadyExists) {
|
||||||
|
props.properties.unshift(prop)
|
||||||
|
}
|
||||||
propsWithInjection = props
|
propsWithInjection = props
|
||||||
} else {
|
} else {
|
||||||
// single v-bind with expression, return a merged replacement
|
// single v-bind with expression, return a merged replacement
|
||||||
@ -262,10 +287,10 @@ export function injectProp(
|
|||||||
props
|
props
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
if (node.callee === RENDER_SLOT) {
|
if (node.type === NodeTypes.VNODE_CALL) {
|
||||||
node.arguments[2] = propsWithInjection
|
node.props = propsWithInjection
|
||||||
} else {
|
} else {
|
||||||
node.arguments[1] = propsWithInjection
|
node.arguments[2] = propsWithInjection
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,19 +2,16 @@
|
|||||||
|
|
||||||
exports[`compile should contain standard transforms 1`] = `
|
exports[`compile should contain standard transforms 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
const { createVNode: _createVNode } = Vue
|
|
||||||
|
|
||||||
const _hoisted_1 = {}
|
return function render(_ctx, _cache) {
|
||||||
|
with (_ctx) {
|
||||||
return function render() {
|
const { createVNode: _createVNode, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||||
with (this) {
|
|
||||||
const { createVNode: _createVNode, createBlock: _createBlock, Fragment: _Fragment, openBlock: _openBlock } = _Vue
|
|
||||||
|
|
||||||
return (_openBlock(), _createBlock(_Fragment, null, [
|
return (_openBlock(), _createBlock(_Fragment, null, [
|
||||||
_createVNode(\\"div\\", { textContent: text }, null, 8 /* PROPS */, [\\"textContent\\"]),
|
_createVNode(\\"div\\", { textContent: text }, null, 8 /* PROPS */, [\\"textContent\\"]),
|
||||||
_createVNode(\\"div\\", { innerHTML: html }, null, 8 /* PROPS */, [\\"innerHTML\\"]),
|
_createVNode(\\"div\\", { innerHTML: html }, null, 8 /* PROPS */, [\\"innerHTML\\"]),
|
||||||
_createVNode(\\"div\\", null, \\"test\\"),
|
_createVNode(\\"div\\", null, \\"test\\"),
|
||||||
_createVNode(\\"div\\", { style: _hoisted_1 }, \\"red\\"),
|
_createVNode(\\"div\\", { style: {\\"color\\":\\"red\\"} }, \\"red\\"),
|
||||||
_createVNode(\\"div\\", { style: {color: 'green'} }, null, 4 /* STYLE */)
|
_createVNode(\\"div\\", { style: {color: 'green'} }, null, 4 /* STYLE */)
|
||||||
], 64 /* STABLE_FRAGMENT */))
|
], 64 /* STABLE_FRAGMENT */))
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ describe('compile', () => {
|
|||||||
const { code } = compile(`<div v-text="text"></div>
|
const { code } = compile(`<div v-text="text"></div>
|
||||||
<div v-html="html"></div>
|
<div v-html="html"></div>
|
||||||
<div v-cloak>test</div>
|
<div v-cloak>test</div>
|
||||||
<div style="color=red">red</div>
|
<div style="color:red">red</div>
|
||||||
<div :style="{color: 'green'}"></div>`)
|
<div :style="{color: 'green'}"></div>`)
|
||||||
|
|
||||||
expect(code).toMatchSnapshot()
|
expect(code).toMatchSnapshot()
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
parse,
|
baseParse as parse,
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
ElementNode,
|
ElementNode,
|
||||||
TextNode,
|
TextNode,
|
||||||
@ -49,6 +49,22 @@ describe('DOM parser', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('textarea support interpolation', () => {
|
||||||
|
const ast = parse('<textarea><div>{{ foo }}</textarea>', parserOptions)
|
||||||
|
const element = ast.children[0] as ElementNode
|
||||||
|
expect(element.children).toMatchObject([
|
||||||
|
{ type: NodeTypes.TEXT, content: `<div>` },
|
||||||
|
{
|
||||||
|
type: NodeTypes.INTERPOLATION,
|
||||||
|
content: {
|
||||||
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
|
content: `foo`,
|
||||||
|
isStatic: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
test('style handles comments/elements as just a text', () => {
|
test('style handles comments/elements as just a text', () => {
|
||||||
const ast = parse(
|
const ast = parse(
|
||||||
'<style>some<div>text</div>and<!--comment--></style>',
|
'<style>some<div>text</div>and<!--comment--></style>',
|
||||||
@ -100,11 +116,36 @@ describe('DOM parser', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('<pre> tag should preserve raw whitespace', () => {
|
test('<pre> tag should preserve raw whitespace', () => {
|
||||||
const rawText = ` \na b \n c`
|
const rawText = ` \na <div>foo \n bar</div> \n c`
|
||||||
|
const ast = parse(`<pre>${rawText}</pre>`, parserOptions)
|
||||||
|
expect((ast.children[0] as ElementNode).children).toMatchObject([
|
||||||
|
{
|
||||||
|
type: NodeTypes.TEXT,
|
||||||
|
content: ` \na `
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: NodeTypes.ELEMENT,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
type: NodeTypes.TEXT,
|
||||||
|
content: `foo \n bar`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: NodeTypes.TEXT,
|
||||||
|
content: ` \n c`
|
||||||
|
}
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
// #908
|
||||||
|
test('<pre> tag should remove leading newline', () => {
|
||||||
|
const rawText = `\nhello`
|
||||||
const ast = parse(`<pre>${rawText}</pre>`, parserOptions)
|
const ast = parse(`<pre>${rawText}</pre>`, parserOptions)
|
||||||
expect((ast.children[0] as ElementNode).children[0]).toMatchObject({
|
expect((ast.children[0] as ElementNode).children[0]).toMatchObject({
|
||||||
type: NodeTypes.TEXT,
|
type: NodeTypes.TEXT,
|
||||||
content: rawText
|
content: rawText.slice(1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -1,23 +1,57 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`compiler: transform v-model input w/ dynamic v-bind 1`] = `
|
||||||
|
"const _Vue = Vue
|
||||||
|
|
||||||
|
return function render(_ctx, _cache) {
|
||||||
|
with (_ctx) {
|
||||||
|
const { vModelDynamic: _vModelDynamic, mergeProps: _mergeProps, createVNode: _createVNode, withDirectives: _withDirectives, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||||
|
|
||||||
|
return _withDirectives((_openBlock(), _createBlock(\\"input\\", _mergeProps(obj, {
|
||||||
|
\\"onUpdate:modelValue\\": $event => (model = $event)
|
||||||
|
}), null, 16 /* FULL_PROPS */, [\\"onUpdate:modelValue\\"])), [
|
||||||
|
[_vModelDynamic, model]
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`compiler: transform v-model input w/ dynamic v-bind 2`] = `
|
||||||
|
"const _Vue = Vue
|
||||||
|
|
||||||
|
return function render(_ctx, _cache) {
|
||||||
|
with (_ctx) {
|
||||||
|
const { vModelDynamic: _vModelDynamic, resolveDirective: _resolveDirective, createVNode: _createVNode, withDirectives: _withDirectives, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||||
|
|
||||||
|
const _directive_bind = _resolveDirective(\\"bind\\")
|
||||||
|
|
||||||
|
return _withDirectives((_openBlock(), _createBlock(\\"input\\", {
|
||||||
|
\\"onUpdate:modelValue\\": $event => (model = $event)
|
||||||
|
}, null, 8 /* PROPS */, [\\"onUpdate:modelValue\\"])), [
|
||||||
|
[_directive_bind, val, key],
|
||||||
|
[_vModelDynamic, model]
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`compiler: transform v-model modifiers .lazy 1`] = `
|
exports[`compiler: transform v-model modifiers .lazy 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { vModelText: _vModelText, createVNode: _createVNode, withDirectives: _withDirectives, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
const { vModelText: _vModelText, createVNode: _createVNode, withDirectives: _withDirectives, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||||
|
|
||||||
return (_openBlock(), _withDirectives(_createBlock(\\"input\\", {
|
return _withDirectives((_openBlock(), _createBlock(\\"input\\", {
|
||||||
modelValue: model,
|
|
||||||
\\"onUpdate:modelValue\\": $event => (model = $event)
|
\\"onUpdate:modelValue\\": $event => (model = $event)
|
||||||
}, null, 8 /* PROPS */, [\\"modelValue\\", \\"onUpdate:modelValue\\"]), [
|
}, null, 8 /* PROPS */, [\\"onUpdate:modelValue\\"])), [
|
||||||
[
|
[
|
||||||
_vModelText,
|
_vModelText,
|
||||||
model,
|
model,
|
||||||
void 0,
|
void 0,
|
||||||
{ lazy: true }
|
{ lazy: true }
|
||||||
]
|
]
|
||||||
]))
|
])
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@ -25,21 +59,20 @@ return function render() {
|
|||||||
exports[`compiler: transform v-model modifiers .number 1`] = `
|
exports[`compiler: transform v-model modifiers .number 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { vModelText: _vModelText, createVNode: _createVNode, withDirectives: _withDirectives, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
const { vModelText: _vModelText, createVNode: _createVNode, withDirectives: _withDirectives, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||||
|
|
||||||
return (_openBlock(), _withDirectives(_createBlock(\\"input\\", {
|
return _withDirectives((_openBlock(), _createBlock(\\"input\\", {
|
||||||
modelValue: model,
|
|
||||||
\\"onUpdate:modelValue\\": $event => (model = $event)
|
\\"onUpdate:modelValue\\": $event => (model = $event)
|
||||||
}, null, 8 /* PROPS */, [\\"modelValue\\", \\"onUpdate:modelValue\\"]), [
|
}, null, 8 /* PROPS */, [\\"onUpdate:modelValue\\"])), [
|
||||||
[
|
[
|
||||||
_vModelText,
|
_vModelText,
|
||||||
model,
|
model,
|
||||||
void 0,
|
void 0,
|
||||||
{ number: true }
|
{ number: true }
|
||||||
]
|
]
|
||||||
]))
|
])
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@ -47,21 +80,20 @@ return function render() {
|
|||||||
exports[`compiler: transform v-model modifiers .trim 1`] = `
|
exports[`compiler: transform v-model modifiers .trim 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { vModelText: _vModelText, createVNode: _createVNode, withDirectives: _withDirectives, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
const { vModelText: _vModelText, createVNode: _createVNode, withDirectives: _withDirectives, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||||
|
|
||||||
return (_openBlock(), _withDirectives(_createBlock(\\"input\\", {
|
return _withDirectives((_openBlock(), _createBlock(\\"input\\", {
|
||||||
modelValue: model,
|
|
||||||
\\"onUpdate:modelValue\\": $event => (model = $event)
|
\\"onUpdate:modelValue\\": $event => (model = $event)
|
||||||
}, null, 8 /* PROPS */, [\\"modelValue\\", \\"onUpdate:modelValue\\"]), [
|
}, null, 8 /* PROPS */, [\\"onUpdate:modelValue\\"])), [
|
||||||
[
|
[
|
||||||
_vModelText,
|
_vModelText,
|
||||||
model,
|
model,
|
||||||
void 0,
|
void 0,
|
||||||
{ trim: true }
|
{ trim: true }
|
||||||
]
|
]
|
||||||
]))
|
])
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@ -69,16 +101,15 @@ return function render() {
|
|||||||
exports[`compiler: transform v-model simple expression 1`] = `
|
exports[`compiler: transform v-model simple expression 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { vModelText: _vModelText, createVNode: _createVNode, withDirectives: _withDirectives, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
const { vModelText: _vModelText, createVNode: _createVNode, withDirectives: _withDirectives, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||||
|
|
||||||
return (_openBlock(), _withDirectives(_createBlock(\\"input\\", {
|
return _withDirectives((_openBlock(), _createBlock(\\"input\\", {
|
||||||
modelValue: model,
|
|
||||||
\\"onUpdate:modelValue\\": $event => (model = $event)
|
\\"onUpdate:modelValue\\": $event => (model = $event)
|
||||||
}, null, 8 /* PROPS */, [\\"modelValue\\", \\"onUpdate:modelValue\\"]), [
|
}, null, 8 /* PROPS */, [\\"onUpdate:modelValue\\"])), [
|
||||||
[_vModelText, model]
|
[_vModelText, model]
|
||||||
]))
|
])
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@ -86,17 +117,16 @@ return function render() {
|
|||||||
exports[`compiler: transform v-model simple expression for input (checkbox) 1`] = `
|
exports[`compiler: transform v-model simple expression for input (checkbox) 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { vModelCheckbox: _vModelCheckbox, createVNode: _createVNode, withDirectives: _withDirectives, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
const { vModelCheckbox: _vModelCheckbox, createVNode: _createVNode, withDirectives: _withDirectives, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||||
|
|
||||||
return (_openBlock(), _withDirectives(_createBlock(\\"input\\", {
|
return _withDirectives((_openBlock(), _createBlock(\\"input\\", {
|
||||||
type: \\"checkbox\\",
|
type: \\"checkbox\\",
|
||||||
modelValue: model,
|
|
||||||
\\"onUpdate:modelValue\\": $event => (model = $event)
|
\\"onUpdate:modelValue\\": $event => (model = $event)
|
||||||
}, null, 8 /* PROPS */, [\\"modelValue\\", \\"onUpdate:modelValue\\"]), [
|
}, null, 8 /* PROPS */, [\\"onUpdate:modelValue\\"])), [
|
||||||
[_vModelCheckbox, model]
|
[_vModelCheckbox, model]
|
||||||
]))
|
])
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@ -104,19 +134,18 @@ return function render() {
|
|||||||
exports[`compiler: transform v-model simple expression for input (dynamic type) 1`] = `
|
exports[`compiler: transform v-model simple expression for input (dynamic type) 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { vModelDynamic: _vModelDynamic, createVNode: _createVNode, withDirectives: _withDirectives, resolveDirective: _resolveDirective, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
const { vModelDynamic: _vModelDynamic, resolveDirective: _resolveDirective, createVNode: _createVNode, withDirectives: _withDirectives, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||||
|
|
||||||
const _directive_bind = _resolveDirective(\\"bind\\")
|
const _directive_bind = _resolveDirective(\\"bind\\")
|
||||||
|
|
||||||
return (_openBlock(), _withDirectives(_createBlock(\\"input\\", {
|
return _withDirectives((_openBlock(), _createBlock(\\"input\\", {
|
||||||
modelValue: model,
|
|
||||||
\\"onUpdate:modelValue\\": $event => (model = $event)
|
\\"onUpdate:modelValue\\": $event => (model = $event)
|
||||||
}, null, 8 /* PROPS */, [\\"modelValue\\", \\"onUpdate:modelValue\\"]), [
|
}, null, 8 /* PROPS */, [\\"onUpdate:modelValue\\"])), [
|
||||||
[_directive_bind, foo, \\"type\\"],
|
[_directive_bind, foo, \\"type\\"],
|
||||||
[_vModelDynamic, model]
|
[_vModelDynamic, model]
|
||||||
]))
|
])
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@ -124,17 +153,16 @@ return function render() {
|
|||||||
exports[`compiler: transform v-model simple expression for input (radio) 1`] = `
|
exports[`compiler: transform v-model simple expression for input (radio) 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { vModelRadio: _vModelRadio, createVNode: _createVNode, withDirectives: _withDirectives, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
const { vModelRadio: _vModelRadio, createVNode: _createVNode, withDirectives: _withDirectives, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||||
|
|
||||||
return (_openBlock(), _withDirectives(_createBlock(\\"input\\", {
|
return _withDirectives((_openBlock(), _createBlock(\\"input\\", {
|
||||||
type: \\"radio\\",
|
type: \\"radio\\",
|
||||||
modelValue: model,
|
|
||||||
\\"onUpdate:modelValue\\": $event => (model = $event)
|
\\"onUpdate:modelValue\\": $event => (model = $event)
|
||||||
}, null, 8 /* PROPS */, [\\"modelValue\\", \\"onUpdate:modelValue\\"]), [
|
}, null, 8 /* PROPS */, [\\"onUpdate:modelValue\\"])), [
|
||||||
[_vModelRadio, model]
|
[_vModelRadio, model]
|
||||||
]))
|
])
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@ -142,17 +170,16 @@ return function render() {
|
|||||||
exports[`compiler: transform v-model simple expression for input (text) 1`] = `
|
exports[`compiler: transform v-model simple expression for input (text) 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { vModelText: _vModelText, createVNode: _createVNode, withDirectives: _withDirectives, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
const { vModelText: _vModelText, createVNode: _createVNode, withDirectives: _withDirectives, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||||
|
|
||||||
return (_openBlock(), _withDirectives(_createBlock(\\"input\\", {
|
return _withDirectives((_openBlock(), _createBlock(\\"input\\", {
|
||||||
type: \\"text\\",
|
type: \\"text\\",
|
||||||
modelValue: model,
|
|
||||||
\\"onUpdate:modelValue\\": $event => (model = $event)
|
\\"onUpdate:modelValue\\": $event => (model = $event)
|
||||||
}, null, 8 /* PROPS */, [\\"modelValue\\", \\"onUpdate:modelValue\\"]), [
|
}, null, 8 /* PROPS */, [\\"onUpdate:modelValue\\"])), [
|
||||||
[_vModelText, model]
|
[_vModelText, model]
|
||||||
]))
|
])
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@ -160,16 +187,15 @@ return function render() {
|
|||||||
exports[`compiler: transform v-model simple expression for select 1`] = `
|
exports[`compiler: transform v-model simple expression for select 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { vModelSelect: _vModelSelect, createVNode: _createVNode, withDirectives: _withDirectives, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
const { vModelSelect: _vModelSelect, createVNode: _createVNode, withDirectives: _withDirectives, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||||
|
|
||||||
return (_openBlock(), _withDirectives(_createBlock(\\"select\\", {
|
return _withDirectives((_openBlock(), _createBlock(\\"select\\", {
|
||||||
modelValue: model,
|
|
||||||
\\"onUpdate:modelValue\\": $event => (model = $event)
|
\\"onUpdate:modelValue\\": $event => (model = $event)
|
||||||
}, null, 8 /* PROPS */, [\\"modelValue\\", \\"onUpdate:modelValue\\"]), [
|
}, null, 8 /* PROPS */, [\\"onUpdate:modelValue\\"])), [
|
||||||
[_vModelSelect, model]
|
[_vModelSelect, model]
|
||||||
]))
|
])
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@ -177,16 +203,15 @@ return function render() {
|
|||||||
exports[`compiler: transform v-model simple expression for textarea 1`] = `
|
exports[`compiler: transform v-model simple expression for textarea 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { vModelText: _vModelText, createVNode: _createVNode, withDirectives: _withDirectives, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
const { vModelText: _vModelText, createVNode: _createVNode, withDirectives: _withDirectives, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||||
|
|
||||||
return (_openBlock(), _withDirectives(_createBlock(\\"textarea\\", {
|
return _withDirectives((_openBlock(), _createBlock(\\"textarea\\", {
|
||||||
modelValue: model,
|
|
||||||
\\"onUpdate:modelValue\\": $event => (model = $event)
|
\\"onUpdate:modelValue\\": $event => (model = $event)
|
||||||
}, null, 8 /* PROPS */, [\\"modelValue\\", \\"onUpdate:modelValue\\"]), [
|
}, null, 8 /* PROPS */, [\\"onUpdate:modelValue\\"])), [
|
||||||
[_vModelText, model]
|
[_vModelText, model]
|
||||||
]))
|
])
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
@ -3,13 +3,13 @@
|
|||||||
exports[`compiler: v-show transform simple expression 1`] = `
|
exports[`compiler: v-show transform simple expression 1`] = `
|
||||||
"const _Vue = Vue
|
"const _Vue = Vue
|
||||||
|
|
||||||
return function render() {
|
return function render(_ctx, _cache) {
|
||||||
with (this) {
|
with (_ctx) {
|
||||||
const { vShow: _vShow, createVNode: _createVNode, withDirectives: _withDirectives, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
const { vShow: _vShow, createVNode: _createVNode, withDirectives: _withDirectives, openBlock: _openBlock, createBlock: _createBlock } = _Vue
|
||||||
|
|
||||||
return (_openBlock(), _withDirectives(_createBlock(\\"div\\", null, null, 32 /* NEED_PATCH */), [
|
return _withDirectives((_openBlock(), _createBlock(\\"div\\", null, null, 512 /* NEED_PATCH */)), [
|
||||||
[_vShow, a]
|
[_vShow, a]
|
||||||
]))
|
])
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
@ -0,0 +1,124 @@
|
|||||||
|
import { compile, NodeTypes, CREATE_STATIC } from '../../src'
|
||||||
|
import {
|
||||||
|
stringifyStatic,
|
||||||
|
StringifyThresholds
|
||||||
|
} from '../../src/transforms/stringifyStatic'
|
||||||
|
|
||||||
|
describe('stringify static html', () => {
|
||||||
|
function compileWithStringify(template: string) {
|
||||||
|
return compile(template, {
|
||||||
|
hoistStatic: true,
|
||||||
|
prefixIdentifiers: true,
|
||||||
|
transformHoist: stringifyStatic
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function repeat(code: string, n: number): string {
|
||||||
|
return new Array(n)
|
||||||
|
.fill(0)
|
||||||
|
.map(() => code)
|
||||||
|
.join('')
|
||||||
|
}
|
||||||
|
|
||||||
|
test('should bail on non-eligible static trees', () => {
|
||||||
|
const { ast } = compileWithStringify(
|
||||||
|
`<div><div><div>hello</div><div>hello</div></div></div>`
|
||||||
|
)
|
||||||
|
expect(ast.hoists.length).toBe(1)
|
||||||
|
// should be a normal vnode call
|
||||||
|
expect(ast.hoists[0].type).toBe(NodeTypes.VNODE_CALL)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should work on eligible content (elements with binding > 5)', () => {
|
||||||
|
const { ast } = compileWithStringify(
|
||||||
|
`<div><div>${repeat(
|
||||||
|
`<span class="foo"/>`,
|
||||||
|
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
|
||||||
|
)}</div></div>`
|
||||||
|
)
|
||||||
|
expect(ast.hoists.length).toBe(1)
|
||||||
|
// should be optimized now
|
||||||
|
expect(ast.hoists[0]).toMatchObject({
|
||||||
|
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||||
|
callee: CREATE_STATIC,
|
||||||
|
arguments: [
|
||||||
|
JSON.stringify(
|
||||||
|
`<div>${repeat(
|
||||||
|
`<span class="foo"></span>`,
|
||||||
|
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
|
||||||
|
)}</div>`
|
||||||
|
)
|
||||||
|
]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should work on eligible content (elements > 20)', () => {
|
||||||
|
const { ast } = compileWithStringify(
|
||||||
|
`<div><div>${repeat(
|
||||||
|
`<span/>`,
|
||||||
|
StringifyThresholds.NODE_COUNT
|
||||||
|
)}</div></div>`
|
||||||
|
)
|
||||||
|
expect(ast.hoists.length).toBe(1)
|
||||||
|
// should be optimized now
|
||||||
|
expect(ast.hoists[0]).toMatchObject({
|
||||||
|
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||||
|
callee: CREATE_STATIC,
|
||||||
|
arguments: [
|
||||||
|
JSON.stringify(
|
||||||
|
`<div>${repeat(
|
||||||
|
`<span></span>`,
|
||||||
|
StringifyThresholds.NODE_COUNT
|
||||||
|
)}</div>`
|
||||||
|
)
|
||||||
|
]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('serliazing constant bindings', () => {
|
||||||
|
const { ast } = compileWithStringify(
|
||||||
|
`<div><div :style="{ color: 'red' }">${repeat(
|
||||||
|
`<span :class="[{ foo: true }, { bar: true }]">{{ 1 }} + {{ false }}</span>`,
|
||||||
|
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
|
||||||
|
)}</div></div>`
|
||||||
|
)
|
||||||
|
expect(ast.hoists.length).toBe(1)
|
||||||
|
// should be optimized now
|
||||||
|
expect(ast.hoists[0]).toMatchObject({
|
||||||
|
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||||
|
callee: CREATE_STATIC,
|
||||||
|
arguments: [
|
||||||
|
JSON.stringify(
|
||||||
|
`<div style="color:red;">${repeat(
|
||||||
|
`<span class="foo bar">1 + false</span>`,
|
||||||
|
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
|
||||||
|
)}</div>`
|
||||||
|
)
|
||||||
|
]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('escape', () => {
|
||||||
|
const { ast } = compileWithStringify(
|
||||||
|
`<div><div>${repeat(
|
||||||
|
`<span :class="'foo' + '>ar'">{{ 1 }} + {{ '<' }}</span>` +
|
||||||
|
`<span>&</span>`,
|
||||||
|
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
|
||||||
|
)}</div></div>`
|
||||||
|
)
|
||||||
|
expect(ast.hoists.length).toBe(1)
|
||||||
|
// should be optimized now
|
||||||
|
expect(ast.hoists[0]).toMatchObject({
|
||||||
|
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||||
|
callee: CREATE_STATIC,
|
||||||
|
arguments: [
|
||||||
|
JSON.stringify(
|
||||||
|
`<div>${repeat(
|
||||||
|
`<span class="foo>ar">1 + <</span>` + `<span>&</span>`,
|
||||||
|
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
|
||||||
|
)}</div>`
|
||||||
|
)
|
||||||
|
]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
@ -1,10 +1,10 @@
|
|||||||
import {
|
import {
|
||||||
parse,
|
baseParse as parse,
|
||||||
transform,
|
transform,
|
||||||
CompilerOptions,
|
CompilerOptions,
|
||||||
ElementNode,
|
ElementNode,
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
CallExpression
|
VNodeCall
|
||||||
} from '@vue/compiler-core'
|
} from '@vue/compiler-core'
|
||||||
import { transformBind } from '../../../compiler-core/src/transforms/vBind'
|
import { transformBind } from '../../../compiler-core/src/transforms/vBind'
|
||||||
import { transformElement } from '../../../compiler-core/src/transforms/transformElement'
|
import { transformElement } from '../../../compiler-core/src/transforms/transformElement'
|
||||||
@ -26,17 +26,8 @@ function transformWithStyleTransform(
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe('compiler: style transform', () => {
|
describe('compiler: style transform', () => {
|
||||||
test('should transform into directive node and hoist value', () => {
|
test('should transform into directive node', () => {
|
||||||
const { root, node } = transformWithStyleTransform(
|
const { node } = transformWithStyleTransform(`<div style="color: red"/>`)
|
||||||
`<div style="color: red"/>`
|
|
||||||
)
|
|
||||||
expect(root.hoists).toMatchObject([
|
|
||||||
{
|
|
||||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
|
||||||
content: `{"color":"red"}`,
|
|
||||||
isStatic: false
|
|
||||||
}
|
|
||||||
])
|
|
||||||
expect(node.props[0]).toMatchObject({
|
expect(node.props[0]).toMatchObject({
|
||||||
type: NodeTypes.DIRECTIVE,
|
type: NodeTypes.DIRECTIVE,
|
||||||
name: `bind`,
|
name: `bind`,
|
||||||
@ -47,7 +38,7 @@ describe('compiler: style transform', () => {
|
|||||||
},
|
},
|
||||||
exp: {
|
exp: {
|
||||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
content: `_hoisted_1`,
|
content: `{"color":"red"}`,
|
||||||
isStatic: false
|
isStatic: false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -60,7 +51,7 @@ describe('compiler: style transform', () => {
|
|||||||
bind: transformBind
|
bind: transformBind
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
expect((node.codegenNode as CallExpression).arguments[1]).toMatchObject({
|
expect((node.codegenNode as VNodeCall).props).toMatchObject({
|
||||||
type: NodeTypes.JS_OBJECT_EXPRESSION,
|
type: NodeTypes.JS_OBJECT_EXPRESSION,
|
||||||
properties: [
|
properties: [
|
||||||
{
|
{
|
||||||
@ -71,13 +62,13 @@ describe('compiler: style transform', () => {
|
|||||||
},
|
},
|
||||||
value: {
|
value: {
|
||||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
content: `_hoisted_1`,
|
content: `{"color":"red"}`,
|
||||||
isStatic: false
|
isStatic: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
// should not cause the STYLE patchFlag to be attached
|
// should not cause the STYLE patchFlag to be attached
|
||||||
expect((node.codegenNode as CallExpression).arguments.length).toBe(2)
|
expect((node.codegenNode as VNodeCall).patchFlag).toBeUndefined()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -1,30 +0,0 @@
|
|||||||
import {
|
|
||||||
parse,
|
|
||||||
transform,
|
|
||||||
ElementNode,
|
|
||||||
CallExpression
|
|
||||||
} from '@vue/compiler-core'
|
|
||||||
import { transformCloak } from '../../src/transforms/vCloak'
|
|
||||||
import { transformElement } from '../../../compiler-core/src/transforms/transformElement'
|
|
||||||
|
|
||||||
function transformWithCloak(template: string) {
|
|
||||||
const ast = parse(template)
|
|
||||||
transform(ast, {
|
|
||||||
nodeTransforms: [transformElement],
|
|
||||||
directiveTransforms: {
|
|
||||||
cloak: transformCloak
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return ast.children[0] as ElementNode
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('compiler: v-cloak transform', () => {
|
|
||||||
test('should add no props to DOM', () => {
|
|
||||||
const node = transformWithCloak(`<div v-cloak/>`)
|
|
||||||
const codegenArgs = (node.codegenNode as CallExpression).arguments
|
|
||||||
|
|
||||||
// As v-cloak adds no properties the codegen should be identical to
|
|
||||||
// rendering a div with no props or reactive data (so just the tag as the arg)
|
|
||||||
expect(codegenArgs.length).toBe(1)
|
|
||||||
})
|
|
||||||
})
|
|
@ -1,5 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
parse,
|
baseParse as parse,
|
||||||
transform,
|
transform,
|
||||||
PlainElementNode,
|
PlainElementNode,
|
||||||
CompilerOptions
|
CompilerOptions
|
||||||
@ -29,15 +29,13 @@ describe('compiler: v-html transform', () => {
|
|||||||
it('should convert v-html to innerHTML', () => {
|
it('should convert v-html to innerHTML', () => {
|
||||||
const ast = transformWithVHtml(`<div v-html="test"/>`)
|
const ast = transformWithVHtml(`<div v-html="test"/>`)
|
||||||
expect((ast.children[0] as PlainElementNode).codegenNode).toMatchObject({
|
expect((ast.children[0] as PlainElementNode).codegenNode).toMatchObject({
|
||||||
arguments: [
|
tag: `"div"`,
|
||||||
`"div"`,
|
props: createObjectMatcher({
|
||||||
createObjectMatcher({
|
innerHTML: `[test]`
|
||||||
innerHTML: `[test]`
|
}),
|
||||||
}),
|
children: undefined,
|
||||||
`null`,
|
patchFlag: genFlagText(PatchFlags.PROPS),
|
||||||
genFlagText(PatchFlags.PROPS),
|
dynamicProps: `["innerHTML"]`
|
||||||
`["innerHTML"]`
|
|
||||||
]
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -50,15 +48,13 @@ describe('compiler: v-html transform', () => {
|
|||||||
[{ code: DOMErrorCodes.X_V_HTML_WITH_CHILDREN }]
|
[{ code: DOMErrorCodes.X_V_HTML_WITH_CHILDREN }]
|
||||||
])
|
])
|
||||||
expect((ast.children[0] as PlainElementNode).codegenNode).toMatchObject({
|
expect((ast.children[0] as PlainElementNode).codegenNode).toMatchObject({
|
||||||
arguments: [
|
tag: `"div"`,
|
||||||
`"div"`,
|
props: createObjectMatcher({
|
||||||
createObjectMatcher({
|
innerHTML: `[test]`
|
||||||
innerHTML: `[test]`
|
}),
|
||||||
}),
|
children: undefined, // <-- children should have been removed
|
||||||
`null`, // <-- children should have been removed
|
patchFlag: genFlagText(PatchFlags.PROPS),
|
||||||
genFlagText(PatchFlags.PROPS),
|
dynamicProps: `["innerHTML"]`
|
||||||
`["innerHTML"]`
|
|
||||||
]
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -1,4 +1,9 @@
|
|||||||
import { parse, transform, CompilerOptions, generate } from '@vue/compiler-core'
|
import {
|
||||||
|
baseParse as parse,
|
||||||
|
transform,
|
||||||
|
CompilerOptions,
|
||||||
|
generate
|
||||||
|
} from '@vue/compiler-core'
|
||||||
import { transformModel } from '../../src/transforms/vModel'
|
import { transformModel } from '../../src/transforms/vModel'
|
||||||
import { transformElement } from '../../../compiler-core/src/transforms/transformElement'
|
import { transformElement } from '../../../compiler-core/src/transforms/transformElement'
|
||||||
import { DOMErrorCodes } from '../../src/errors'
|
import { DOMErrorCodes } from '../../src/errors'
|
||||||
@ -58,6 +63,19 @@ describe('compiler: transform v-model', () => {
|
|||||||
expect(generate(root).code).toMatchSnapshot()
|
expect(generate(root).code).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('input w/ dynamic v-bind', () => {
|
||||||
|
const root = transformWithModel('<input v-bind="obj" v-model="model" />')
|
||||||
|
|
||||||
|
expect(root.helpers).toContain(V_MODEL_DYNAMIC)
|
||||||
|
expect(generate(root).code).toMatchSnapshot()
|
||||||
|
|
||||||
|
const root2 = transformWithModel(
|
||||||
|
'<input v-bind:[key]="val" v-model="model" />'
|
||||||
|
)
|
||||||
|
expect(root2.helpers).toContain(V_MODEL_DYNAMIC)
|
||||||
|
expect(generate(root2).code).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
test('simple expression for select', () => {
|
test('simple expression for select', () => {
|
||||||
const root = transformWithModel('<select v-model="model" />')
|
const root = transformWithModel('<select v-model="model" />')
|
||||||
|
|
||||||
|
@ -1,17 +1,21 @@
|
|||||||
import {
|
import {
|
||||||
parse,
|
baseParse as parse,
|
||||||
transform,
|
transform,
|
||||||
CompilerOptions,
|
CompilerOptions,
|
||||||
ElementNode,
|
ElementNode,
|
||||||
ObjectExpression,
|
ObjectExpression,
|
||||||
CallExpression,
|
NodeTypes,
|
||||||
NodeTypes
|
VNodeCall
|
||||||
} from '@vue/compiler-core'
|
} from '@vue/compiler-core'
|
||||||
import { transformOn } from '../../src/transforms/vOn'
|
import { transformOn } from '../../src/transforms/vOn'
|
||||||
import { V_ON_WITH_MODIFIERS, V_ON_WITH_KEYS } from '../../src/runtimeHelpers'
|
import { V_ON_WITH_MODIFIERS, V_ON_WITH_KEYS } from '../../src/runtimeHelpers'
|
||||||
import { transformElement } from '../../../compiler-core/src/transforms/transformElement'
|
import { transformElement } from '../../../compiler-core/src/transforms/transformElement'
|
||||||
import { transformExpression } from '../../../compiler-core/src/transforms/transformExpression'
|
import { transformExpression } from '../../../compiler-core/src/transforms/transformExpression'
|
||||||
import { createObjectMatcher } from '../../../compiler-core/__tests__/testUtils'
|
import {
|
||||||
|
createObjectMatcher,
|
||||||
|
genFlagText
|
||||||
|
} from '../../../compiler-core/__tests__/testUtils'
|
||||||
|
import { PatchFlags } from '@vue/shared'
|
||||||
|
|
||||||
function parseWithVOn(template: string, options: CompilerOptions = {}) {
|
function parseWithVOn(template: string, options: CompilerOptions = {}) {
|
||||||
const ast = parse(template)
|
const ast = parse(template)
|
||||||
@ -24,8 +28,8 @@ function parseWithVOn(template: string, options: CompilerOptions = {}) {
|
|||||||
})
|
})
|
||||||
return {
|
return {
|
||||||
root: ast,
|
root: ast,
|
||||||
props: (((ast.children[0] as ElementNode).codegenNode as CallExpression)
|
props: (((ast.children[0] as ElementNode).codegenNode as VNodeCall)
|
||||||
.arguments[1] as ObjectExpression).properties
|
.props as ObjectExpression).properties
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,6 +152,58 @@ describe('compiler-dom: transform v-on', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('should transform click.right', () => {
|
||||||
|
const {
|
||||||
|
props: [prop]
|
||||||
|
} = parseWithVOn(`<div @click.right="test"/>`)
|
||||||
|
expect(prop.key).toMatchObject({
|
||||||
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
|
content: `onContextmenu`
|
||||||
|
})
|
||||||
|
|
||||||
|
// dynamic
|
||||||
|
const {
|
||||||
|
props: [prop2]
|
||||||
|
} = parseWithVOn(`<div @[event].right="test"/>`)
|
||||||
|
// ("on" + (event)).toLowerCase() === "onclick" ? "onContextmenu" : ("on" + (event))
|
||||||
|
expect(prop2.key).toMatchObject({
|
||||||
|
type: NodeTypes.COMPOUND_EXPRESSION,
|
||||||
|
children: [
|
||||||
|
`(`,
|
||||||
|
{ children: [`"on" + (`, { content: 'event' }, `)`] },
|
||||||
|
`).toLowerCase() === "onclick" ? "onContextmenu" : (`,
|
||||||
|
{ children: [`"on" + (`, { content: 'event' }, `)`] },
|
||||||
|
`)`
|
||||||
|
]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should transform click.middle', () => {
|
||||||
|
const {
|
||||||
|
props: [prop]
|
||||||
|
} = parseWithVOn(`<div @click.middle="test"/>`)
|
||||||
|
expect(prop.key).toMatchObject({
|
||||||
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
|
content: `onMouseup`
|
||||||
|
})
|
||||||
|
|
||||||
|
// dynamic
|
||||||
|
const {
|
||||||
|
props: [prop2]
|
||||||
|
} = parseWithVOn(`<div @[event].middle="test"/>`)
|
||||||
|
// ("on" + (event)).toLowerCase() === "onclick" ? "onMouseup" : ("on" + (event))
|
||||||
|
expect(prop2.key).toMatchObject({
|
||||||
|
type: NodeTypes.COMPOUND_EXPRESSION,
|
||||||
|
children: [
|
||||||
|
`(`,
|
||||||
|
{ children: [`"on" + (`, { content: 'event' }, `)`] },
|
||||||
|
`).toLowerCase() === "onclick" ? "onMouseup" : (`,
|
||||||
|
{ children: [`"on" + (`, { content: 'event' }, `)`] },
|
||||||
|
`)`
|
||||||
|
]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
test('cache handler w/ modifiers', () => {
|
test('cache handler w/ modifiers', () => {
|
||||||
const {
|
const {
|
||||||
root,
|
root,
|
||||||
@ -157,8 +213,11 @@ describe('compiler-dom: transform v-on', () => {
|
|||||||
cacheHandlers: true
|
cacheHandlers: true
|
||||||
})
|
})
|
||||||
expect(root.cached).toBe(1)
|
expect(root.cached).toBe(1)
|
||||||
// should not treat cached handler as dynamicProp, so no flags
|
// should not treat cached handler as dynamicProp, so it should have no
|
||||||
expect((root as any).children[0].codegenNode.arguments.length).toBe(2)
|
// dynamicProps flags and only the hydration flag
|
||||||
|
expect((root as any).children[0].codegenNode.patchFlag).toBe(
|
||||||
|
genFlagText(PatchFlags.HYDRATE_EVENTS)
|
||||||
|
)
|
||||||
expect(prop.value).toMatchObject({
|
expect(prop.value).toMatchObject({
|
||||||
type: NodeTypes.JS_CACHE_EXPRESSION,
|
type: NodeTypes.JS_CACHE_EXPRESSION,
|
||||||
index: 1,
|
index: 1,
|
||||||
|
@ -1,4 +1,9 @@
|
|||||||
import { parse, transform, generate, CompilerOptions } from '@vue/compiler-core'
|
import {
|
||||||
|
baseParse as parse,
|
||||||
|
transform,
|
||||||
|
generate,
|
||||||
|
CompilerOptions
|
||||||
|
} from '@vue/compiler-core'
|
||||||
import { transformElement } from '../../../compiler-core/src/transforms/transformElement'
|
import { transformElement } from '../../../compiler-core/src/transforms/transformElement'
|
||||||
import { transformShow } from '../../src/transforms/vShow'
|
import { transformShow } from '../../src/transforms/vShow'
|
||||||
import { DOMErrorCodes } from '../../src/errors'
|
import { DOMErrorCodes } from '../../src/errors'
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
parse,
|
baseParse as parse,
|
||||||
transform,
|
transform,
|
||||||
PlainElementNode,
|
PlainElementNode,
|
||||||
CompilerOptions
|
CompilerOptions
|
||||||
@ -29,15 +29,13 @@ describe('compiler: v-text transform', () => {
|
|||||||
it('should convert v-text to textContent', () => {
|
it('should convert v-text to textContent', () => {
|
||||||
const ast = transformWithVText(`<div v-text="test"/>`)
|
const ast = transformWithVText(`<div v-text="test"/>`)
|
||||||
expect((ast.children[0] as PlainElementNode).codegenNode).toMatchObject({
|
expect((ast.children[0] as PlainElementNode).codegenNode).toMatchObject({
|
||||||
arguments: [
|
tag: `"div"`,
|
||||||
`"div"`,
|
props: createObjectMatcher({
|
||||||
createObjectMatcher({
|
textContent: `[test]`
|
||||||
textContent: `[test]`
|
}),
|
||||||
}),
|
children: undefined,
|
||||||
`null`,
|
patchFlag: genFlagText(PatchFlags.PROPS),
|
||||||
genFlagText(PatchFlags.PROPS),
|
dynamicProps: `["textContent"]`
|
||||||
`["textContent"]`
|
|
||||||
]
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -50,15 +48,13 @@ describe('compiler: v-text transform', () => {
|
|||||||
[{ code: DOMErrorCodes.X_V_TEXT_WITH_CHILDREN }]
|
[{ code: DOMErrorCodes.X_V_TEXT_WITH_CHILDREN }]
|
||||||
])
|
])
|
||||||
expect((ast.children[0] as PlainElementNode).codegenNode).toMatchObject({
|
expect((ast.children[0] as PlainElementNode).codegenNode).toMatchObject({
|
||||||
arguments: [
|
tag: `"div"`,
|
||||||
`"div"`,
|
props: createObjectMatcher({
|
||||||
createObjectMatcher({
|
textContent: `[test]`
|
||||||
textContent: `[test]`
|
}),
|
||||||
}),
|
children: undefined, // <-- children should have been removed
|
||||||
`null`, // <-- children should have been removed
|
patchFlag: genFlagText(PatchFlags.PROPS),
|
||||||
genFlagText(PatchFlags.PROPS),
|
dynamicProps: `["textContent"]`
|
||||||
`["textContent"]`
|
|
||||||
]
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -0,0 +1,140 @@
|
|||||||
|
import { compile } from '../../src'
|
||||||
|
|
||||||
|
describe('compiler warnings', () => {
|
||||||
|
describe('Transition', () => {
|
||||||
|
function checkWarning(
|
||||||
|
template: string,
|
||||||
|
shouldWarn: boolean,
|
||||||
|
message = `<Transition> expects exactly one child element or component.`
|
||||||
|
) {
|
||||||
|
const spy = jest.fn()
|
||||||
|
compile(template.trim(), {
|
||||||
|
hoistStatic: true,
|
||||||
|
transformHoist: null,
|
||||||
|
onError: err => {
|
||||||
|
spy(err.message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (shouldWarn) expect(spy).toHaveBeenCalledWith(message)
|
||||||
|
else expect(spy).not.toHaveBeenCalled()
|
||||||
|
}
|
||||||
|
|
||||||
|
test('warns if multiple children', () => {
|
||||||
|
checkWarning(
|
||||||
|
`
|
||||||
|
<transition>
|
||||||
|
<div>hey</div>
|
||||||
|
<div>hey</div>
|
||||||
|
</transition>
|
||||||
|
`,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('warns with v-for', () => {
|
||||||
|
checkWarning(
|
||||||
|
`
|
||||||
|
<transition>
|
||||||
|
<div v-for="i in items">hey</div>
|
||||||
|
</transition>
|
||||||
|
`,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('warns with multiple v-if + v-for', () => {
|
||||||
|
checkWarning(
|
||||||
|
`
|
||||||
|
<transition>
|
||||||
|
<div v-if="a" v-for="i in items">hey</div>
|
||||||
|
<div v-else v-for="i in items">hey</div>
|
||||||
|
</transition>
|
||||||
|
`,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('warns with template v-if', () => {
|
||||||
|
checkWarning(
|
||||||
|
`
|
||||||
|
<transition>
|
||||||
|
<template v-if="ok"></template>
|
||||||
|
</transition>
|
||||||
|
`,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('warns with multiple templates', () => {
|
||||||
|
checkWarning(
|
||||||
|
`
|
||||||
|
<transition>
|
||||||
|
<template v-if="a"></template>
|
||||||
|
<template v-else></template>
|
||||||
|
</transition>
|
||||||
|
`,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('warns if multiple children with v-if', () => {
|
||||||
|
checkWarning(
|
||||||
|
`
|
||||||
|
<transition>
|
||||||
|
<div v-if="one">hey</div>
|
||||||
|
<div v-if="other">hey</div>
|
||||||
|
</transition>
|
||||||
|
`,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('does not warn with regular element', () => {
|
||||||
|
checkWarning(
|
||||||
|
`
|
||||||
|
<transition>
|
||||||
|
<div>hey</div>
|
||||||
|
</transition>
|
||||||
|
`,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('does not warn with one single v-if', () => {
|
||||||
|
checkWarning(
|
||||||
|
`
|
||||||
|
<transition>
|
||||||
|
<div v-if="a">hey</div>
|
||||||
|
</transition>
|
||||||
|
`,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('does not warn with v-if v-else-if v-else', () => {
|
||||||
|
checkWarning(
|
||||||
|
`
|
||||||
|
<transition>
|
||||||
|
<div v-if="a">hey</div>
|
||||||
|
<div v-else-if="b">hey</div>
|
||||||
|
<div v-else>hey</div>
|
||||||
|
</transition>
|
||||||
|
`,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('does not warn with v-if v-else', () => {
|
||||||
|
checkWarning(
|
||||||
|
`
|
||||||
|
<transition>
|
||||||
|
<div v-if="a">hey</div>
|
||||||
|
<div v-else>hey</div>
|
||||||
|
</transition>
|
||||||
|
`,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
@ -1,18 +1,18 @@
|
|||||||
{
|
{
|
||||||
"name": "@vue/compiler-dom",
|
"name": "@vue/compiler-dom",
|
||||||
"version": "3.0.0-alpha.0",
|
"version": "3.0.0-alpha.11",
|
||||||
"description": "@vue/compiler-dom",
|
"description": "@vue/compiler-dom",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"module": "dist/compiler-dom.esm-bundler.js",
|
"module": "dist/compiler-dom.esm-bundler.js",
|
||||||
|
"types": "dist/compiler-dom.d.ts",
|
||||||
|
"unpkg": "dist/compiler-dom/global.js",
|
||||||
"files": [
|
"files": [
|
||||||
"index.js",
|
"index.js",
|
||||||
"dist"
|
"dist"
|
||||||
],
|
],
|
||||||
"types": "dist/compiler-dom.d.ts",
|
|
||||||
"unpkg": "dist/compiler-dom/global.js",
|
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
"buildOptions": {
|
"buildOptions": {
|
||||||
"name": "VueDOMCompiler",
|
"name": "VueCompilerDOM",
|
||||||
"formats": [
|
"formats": [
|
||||||
"esm-bundler",
|
"esm-bundler",
|
||||||
"cjs",
|
"cjs",
|
||||||
@ -22,7 +22,7 @@
|
|||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/vuejs/vue.git"
|
"url": "git+https://github.com/vuejs/vue-next.git"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"vue"
|
"vue"
|
||||||
@ -30,10 +30,11 @@
|
|||||||
"author": "Evan You",
|
"author": "Evan You",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/vuejs/vue/issues"
|
"url": "https://github.com/vuejs/vue-next/issues"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/vuejs/vue/tree/dev/packages/compiler-dom#readme",
|
"homepage": "https://github.com/vuejs/vue-next/tree/master/packages/compiler-dom#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/compiler-core": "3.0.0-alpha.0"
|
"@vue/shared": "3.0.0-alpha.11",
|
||||||
|
"@vue/compiler-core": "3.0.0-alpha.11"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,10 @@ export const enum DOMErrorCodes {
|
|||||||
X_V_MODEL_ON_INVALID_ELEMENT,
|
X_V_MODEL_ON_INVALID_ELEMENT,
|
||||||
X_V_MODEL_ARG_ON_ELEMENT,
|
X_V_MODEL_ARG_ON_ELEMENT,
|
||||||
X_V_MODEL_ON_FILE_INPUT_ELEMENT,
|
X_V_MODEL_ON_FILE_INPUT_ELEMENT,
|
||||||
X_V_SHOW_NO_EXPRESSION
|
X_V_MODEL_UNNECESSARY_VALUE,
|
||||||
|
X_V_SHOW_NO_EXPRESSION,
|
||||||
|
X_TRANSITION_INVALID_CHILDREN,
|
||||||
|
__EXTEND_POINT__
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DOMErrorMessages: { [code: number]: string } = {
|
export const DOMErrorMessages: { [code: number]: string } = {
|
||||||
@ -39,5 +42,7 @@ export const DOMErrorMessages: { [code: number]: string } = {
|
|||||||
[DOMErrorCodes.X_V_MODEL_ON_INVALID_ELEMENT]: `v-model can only be used on <input>, <textarea> and <select> elements.`,
|
[DOMErrorCodes.X_V_MODEL_ON_INVALID_ELEMENT]: `v-model can only be used on <input>, <textarea> and <select> elements.`,
|
||||||
[DOMErrorCodes.X_V_MODEL_ARG_ON_ELEMENT]: `v-model argument is not supported on plain elements.`,
|
[DOMErrorCodes.X_V_MODEL_ARG_ON_ELEMENT]: `v-model argument is not supported on plain elements.`,
|
||||||
[DOMErrorCodes.X_V_MODEL_ON_FILE_INPUT_ELEMENT]: `v-model cannot used on file inputs since they are read-only. Use a v-on:change listener instead.`,
|
[DOMErrorCodes.X_V_MODEL_ON_FILE_INPUT_ELEMENT]: `v-model cannot used on file inputs since they are read-only. Use a v-on:change listener instead.`,
|
||||||
[DOMErrorCodes.X_V_SHOW_NO_EXPRESSION]: `v-show is missing expression.`
|
[DOMErrorCodes.X_V_MODEL_UNNECESSARY_VALUE]: `Unnecessary value binding used alongside v-model. It will interfere with v-model's behavior.`,
|
||||||
|
[DOMErrorCodes.X_V_SHOW_NO_EXPRESSION]: `v-show is missing expression.`,
|
||||||
|
[DOMErrorCodes.X_TRANSITION_INVALID_CHILDREN]: `<Transition> expects exactly one child element or component.`
|
||||||
}
|
}
|
||||||
|
@ -1,45 +1,67 @@
|
|||||||
import {
|
import {
|
||||||
baseCompile,
|
baseCompile,
|
||||||
|
baseParse,
|
||||||
CompilerOptions,
|
CompilerOptions,
|
||||||
CodegenResult,
|
CodegenResult,
|
||||||
isBuiltInType
|
ParserOptions,
|
||||||
|
RootNode,
|
||||||
|
noopDirectiveTransform,
|
||||||
|
NodeTransform,
|
||||||
|
DirectiveTransform
|
||||||
} from '@vue/compiler-core'
|
} from '@vue/compiler-core'
|
||||||
import { parserOptionsMinimal } from './parserOptionsMinimal'
|
import { parserOptionsMinimal } from './parserOptionsMinimal'
|
||||||
import { parserOptionsStandard } from './parserOptionsStandard'
|
import { parserOptionsStandard } from './parserOptionsStandard'
|
||||||
import { transformStyle } from './transforms/transformStyle'
|
import { transformStyle } from './transforms/transformStyle'
|
||||||
import { transformCloak } from './transforms/vCloak'
|
|
||||||
import { transformVHtml } from './transforms/vHtml'
|
import { transformVHtml } from './transforms/vHtml'
|
||||||
import { transformVText } from './transforms/vText'
|
import { transformVText } from './transforms/vText'
|
||||||
import { transformModel } from './transforms/vModel'
|
import { transformModel } from './transforms/vModel'
|
||||||
import { transformOn } from './transforms/vOn'
|
import { transformOn } from './transforms/vOn'
|
||||||
import { transformShow } from './transforms/vShow'
|
import { transformShow } from './transforms/vShow'
|
||||||
import { TRANSITION, TRANSITION_GROUP } from './runtimeHelpers'
|
import { warnTransitionChildren } from './transforms/warnTransitionChildren'
|
||||||
|
import { stringifyStatic } from './transforms/stringifyStatic'
|
||||||
|
|
||||||
|
export const parserOptions = __BROWSER__
|
||||||
|
? parserOptionsMinimal
|
||||||
|
: parserOptionsStandard
|
||||||
|
|
||||||
|
export const DOMNodeTransforms: NodeTransform[] = [
|
||||||
|
transformStyle,
|
||||||
|
...(__DEV__ ? [warnTransitionChildren] : [])
|
||||||
|
]
|
||||||
|
|
||||||
|
export const DOMDirectiveTransforms: Record<string, DirectiveTransform> = {
|
||||||
|
cloak: noopDirectiveTransform,
|
||||||
|
html: transformVHtml,
|
||||||
|
text: transformVText,
|
||||||
|
model: transformModel, // override compiler-core
|
||||||
|
on: transformOn, // override compiler-core
|
||||||
|
show: transformShow
|
||||||
|
}
|
||||||
|
|
||||||
export function compile(
|
export function compile(
|
||||||
template: string,
|
template: string,
|
||||||
options: CompilerOptions = {}
|
options: CompilerOptions = {}
|
||||||
): CodegenResult {
|
): CodegenResult {
|
||||||
return baseCompile(template, {
|
return baseCompile(template, {
|
||||||
|
...parserOptions,
|
||||||
...options,
|
...options,
|
||||||
...(__BROWSER__ ? parserOptionsMinimal : parserOptionsStandard),
|
nodeTransforms: [...DOMNodeTransforms, ...(options.nodeTransforms || [])],
|
||||||
nodeTransforms: [transformStyle, ...(options.nodeTransforms || [])],
|
|
||||||
directiveTransforms: {
|
directiveTransforms: {
|
||||||
cloak: transformCloak,
|
...DOMDirectiveTransforms,
|
||||||
html: transformVHtml,
|
|
||||||
text: transformVText,
|
|
||||||
model: transformModel, // override compiler-core
|
|
||||||
on: transformOn,
|
|
||||||
show: transformShow,
|
|
||||||
...(options.directiveTransforms || {})
|
...(options.directiveTransforms || {})
|
||||||
},
|
},
|
||||||
isBuiltInComponent: tag => {
|
transformHoist: __BROWSER__ ? null : stringifyStatic
|
||||||
if (isBuiltInType(tag, `Transition`)) {
|
|
||||||
return TRANSITION
|
|
||||||
} else if (isBuiltInType(tag, `TransitionGroup`)) {
|
|
||||||
return TRANSITION_GROUP
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function parse(template: string, options: ParserOptions = {}): RootNode {
|
||||||
|
return baseParse(template, {
|
||||||
|
...parserOptions,
|
||||||
|
...options
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export * from './runtimeHelpers'
|
||||||
|
export { transformStyle } from './transforms/transformStyle'
|
||||||
|
export { createDOMCompilerError, DOMErrorCodes } from './errors'
|
||||||
export * from '@vue/compiler-core'
|
export * from '@vue/compiler-core'
|
||||||
|
@ -3,9 +3,11 @@ import {
|
|||||||
ParserOptions,
|
ParserOptions,
|
||||||
ElementNode,
|
ElementNode,
|
||||||
Namespaces,
|
Namespaces,
|
||||||
NodeTypes
|
NodeTypes,
|
||||||
|
isBuiltInType
|
||||||
} from '@vue/compiler-core'
|
} from '@vue/compiler-core'
|
||||||
import { makeMap, isVoidTag, isHTMLTag, isSVGTag } from '@vue/shared'
|
import { makeMap, isVoidTag, isHTMLTag, isSVGTag } from '@vue/shared'
|
||||||
|
import { TRANSITION, TRANSITION_GROUP } from './runtimeHelpers'
|
||||||
|
|
||||||
const isRawTextContainer = /*#__PURE__*/ makeMap(
|
const isRawTextContainer = /*#__PURE__*/ makeMap(
|
||||||
'style,iframe,script,noscript',
|
'style,iframe,script,noscript',
|
||||||
@ -23,6 +25,14 @@ export const parserOptionsMinimal: ParserOptions = {
|
|||||||
isNativeTag: tag => isHTMLTag(tag) || isSVGTag(tag),
|
isNativeTag: tag => isHTMLTag(tag) || isSVGTag(tag),
|
||||||
isPreTag: tag => tag === 'pre',
|
isPreTag: tag => tag === 'pre',
|
||||||
|
|
||||||
|
isBuiltInComponent: (tag: string): symbol | undefined => {
|
||||||
|
if (isBuiltInType(tag, `Transition`)) {
|
||||||
|
return TRANSITION
|
||||||
|
} else if (isBuiltInType(tag, `TransitionGroup`)) {
|
||||||
|
return TRANSITION_GROUP
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/parsing.html#tree-construction-dispatcher
|
// https://html.spec.whatwg.org/multipage/parsing.html#tree-construction-dispatcher
|
||||||
getNamespace(tag: string, parent: ElementNode | undefined): DOMNamespaces {
|
getNamespace(tag: string, parent: ElementNode | undefined): DOMNamespaces {
|
||||||
let ns = parent ? parent.ns : DOMNamespaces.HTML
|
let ns = parent ? parent.ns : DOMNamespaces.HTML
|
||||||
|
171
packages/compiler-dom/src/transforms/stringifyStatic.ts
Normal file
171
packages/compiler-dom/src/transforms/stringifyStatic.ts
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
import {
|
||||||
|
NodeTypes,
|
||||||
|
ElementNode,
|
||||||
|
TransformContext,
|
||||||
|
TemplateChildNode,
|
||||||
|
SimpleExpressionNode,
|
||||||
|
createCallExpression,
|
||||||
|
HoistTransform,
|
||||||
|
CREATE_STATIC,
|
||||||
|
ExpressionNode
|
||||||
|
} from '@vue/compiler-core'
|
||||||
|
import {
|
||||||
|
isVoidTag,
|
||||||
|
isString,
|
||||||
|
isSymbol,
|
||||||
|
escapeHtml,
|
||||||
|
toDisplayString,
|
||||||
|
normalizeClass,
|
||||||
|
normalizeStyle,
|
||||||
|
stringifyStyle
|
||||||
|
} from '@vue/shared'
|
||||||
|
|
||||||
|
// Turn eligible hoisted static trees into stringied static nodes, e.g.
|
||||||
|
// const _hoisted_1 = createStaticVNode(`<div class="foo">bar</div>`)
|
||||||
|
// This is only performed in non-in-browser compilations.
|
||||||
|
export const stringifyStatic: HoistTransform = (node, context) => {
|
||||||
|
if (shouldOptimize(node)) {
|
||||||
|
return createCallExpression(context.helper(CREATE_STATIC), [
|
||||||
|
JSON.stringify(stringifyElement(node, context))
|
||||||
|
])
|
||||||
|
} else {
|
||||||
|
return node.codegenNode!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const enum StringifyThresholds {
|
||||||
|
ELEMENT_WITH_BINDING_COUNT = 5,
|
||||||
|
NODE_COUNT = 20
|
||||||
|
}
|
||||||
|
|
||||||
|
// Opt-in heuristics based on:
|
||||||
|
// 1. number of elements with attributes > 5.
|
||||||
|
// 2. OR: number of total nodes > 20
|
||||||
|
// For some simple trees, the performance can actually be worse.
|
||||||
|
// it is only worth it when the tree is complex enough
|
||||||
|
// (e.g. big piece of static content)
|
||||||
|
function shouldOptimize(node: ElementNode): boolean {
|
||||||
|
let bindingThreshold = StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
|
||||||
|
let nodeThreshold = StringifyThresholds.NODE_COUNT
|
||||||
|
|
||||||
|
// TODO: check for cases where using innerHTML will result in different
|
||||||
|
// output compared to imperative node insertions.
|
||||||
|
// probably only need to check for most common case
|
||||||
|
// i.e. non-phrasing-content tags inside `<p>`
|
||||||
|
function walk(node: ElementNode) {
|
||||||
|
for (let i = 0; i < node.children.length; i++) {
|
||||||
|
if (--nodeThreshold === 0) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
const child = node.children[i]
|
||||||
|
if (child.type === NodeTypes.ELEMENT) {
|
||||||
|
if (child.props.length > 0 && --bindingThreshold === 0) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (walk(child)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return walk(node)
|
||||||
|
}
|
||||||
|
|
||||||
|
function stringifyElement(
|
||||||
|
node: ElementNode,
|
||||||
|
context: TransformContext
|
||||||
|
): string {
|
||||||
|
let res = `<${node.tag}`
|
||||||
|
for (let i = 0; i < node.props.length; i++) {
|
||||||
|
const p = node.props[i]
|
||||||
|
if (p.type === NodeTypes.ATTRIBUTE) {
|
||||||
|
res += ` ${p.name}`
|
||||||
|
if (p.value) {
|
||||||
|
res += `="${escapeHtml(p.value.content)}"`
|
||||||
|
}
|
||||||
|
} else if (p.type === NodeTypes.DIRECTIVE && p.name === 'bind') {
|
||||||
|
// constant v-bind, e.g. :foo="1"
|
||||||
|
let evaluated = evaluateConstant(p.exp as SimpleExpressionNode)
|
||||||
|
const arg = p.arg && (p.arg as SimpleExpressionNode).content
|
||||||
|
if (arg === 'class') {
|
||||||
|
evaluated = normalizeClass(evaluated)
|
||||||
|
} else if (arg === 'style') {
|
||||||
|
evaluated = stringifyStyle(normalizeStyle(evaluated))
|
||||||
|
}
|
||||||
|
res += ` ${(p.arg as SimpleExpressionNode).content}="${escapeHtml(
|
||||||
|
evaluated
|
||||||
|
)}"`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (context.scopeId) {
|
||||||
|
res += ` ${context.scopeId}`
|
||||||
|
}
|
||||||
|
res += `>`
|
||||||
|
for (let i = 0; i < node.children.length; i++) {
|
||||||
|
res += stringifyNode(node.children[i], context)
|
||||||
|
}
|
||||||
|
if (!isVoidTag(node.tag)) {
|
||||||
|
res += `</${node.tag}>`
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
function stringifyNode(
|
||||||
|
node: string | TemplateChildNode,
|
||||||
|
context: TransformContext
|
||||||
|
): string {
|
||||||
|
if (isString(node)) {
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
if (isSymbol(node)) {
|
||||||
|
return ``
|
||||||
|
}
|
||||||
|
switch (node.type) {
|
||||||
|
case NodeTypes.ELEMENT:
|
||||||
|
return stringifyElement(node, context)
|
||||||
|
case NodeTypes.TEXT:
|
||||||
|
return escapeHtml(node.content)
|
||||||
|
case NodeTypes.COMMENT:
|
||||||
|
return `<!--${escapeHtml(node.content)}-->`
|
||||||
|
case NodeTypes.INTERPOLATION:
|
||||||
|
return escapeHtml(toDisplayString(evaluateConstant(node.content)))
|
||||||
|
case NodeTypes.COMPOUND_EXPRESSION:
|
||||||
|
return escapeHtml(evaluateConstant(node))
|
||||||
|
case NodeTypes.TEXT_CALL:
|
||||||
|
return stringifyNode(node.content, context)
|
||||||
|
default:
|
||||||
|
// static trees will not contain if/for nodes
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// __UNSAFE__
|
||||||
|
// Reason: eval.
|
||||||
|
// It's technically safe to eval because only constant expressions are possible
|
||||||
|
// here, e.g. `{{ 1 }}` or `{{ 'foo' }}`
|
||||||
|
// in addition, constant exps bail on presence of parens so you can't even
|
||||||
|
// run JSFuck in here. But we mark it unsafe for security review purposes.
|
||||||
|
// (see compiler-core/src/transformExpressions)
|
||||||
|
function evaluateConstant(exp: ExpressionNode): string {
|
||||||
|
if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {
|
||||||
|
return new Function(`return ${exp.content}`)()
|
||||||
|
} else {
|
||||||
|
// compound
|
||||||
|
let res = ``
|
||||||
|
exp.children.forEach(c => {
|
||||||
|
if (isString(c) || isSymbol(c)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (c.type === NodeTypes.TEXT) {
|
||||||
|
res += c.content
|
||||||
|
} else if (c.type === NodeTypes.INTERPOLATION) {
|
||||||
|
res += toDisplayString(evaluateConstant(c.content))
|
||||||
|
} else {
|
||||||
|
res += evaluateConstant(c)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,9 @@
|
|||||||
import {
|
import {
|
||||||
NodeTransform,
|
NodeTransform,
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
createSimpleExpression
|
createSimpleExpression,
|
||||||
|
SimpleExpressionNode,
|
||||||
|
SourceLocation
|
||||||
} from '@vue/compiler-core'
|
} from '@vue/compiler-core'
|
||||||
|
|
||||||
// Parse inline CSS strings for static style attributes into an object.
|
// Parse inline CSS strings for static style attributes into an object.
|
||||||
@ -15,13 +17,11 @@ export const transformStyle: NodeTransform = (node, context) => {
|
|||||||
node.props.forEach((p, i) => {
|
node.props.forEach((p, i) => {
|
||||||
if (p.type === NodeTypes.ATTRIBUTE && p.name === 'style' && p.value) {
|
if (p.type === NodeTypes.ATTRIBUTE && p.name === 'style' && p.value) {
|
||||||
// replace p with an expression node
|
// replace p with an expression node
|
||||||
const parsed = JSON.stringify(parseInlineCSS(p.value.content))
|
|
||||||
const exp = context.hoist(createSimpleExpression(parsed, false, p.loc))
|
|
||||||
node.props[i] = {
|
node.props[i] = {
|
||||||
type: NodeTypes.DIRECTIVE,
|
type: NodeTypes.DIRECTIVE,
|
||||||
name: `bind`,
|
name: `bind`,
|
||||||
arg: createSimpleExpression(`style`, true, p.loc),
|
arg: createSimpleExpression(`style`, true, p.loc),
|
||||||
exp,
|
exp: parseInlineCSS(p.value.content, p.loc),
|
||||||
modifiers: [],
|
modifiers: [],
|
||||||
loc: p.loc
|
loc: p.loc
|
||||||
}
|
}
|
||||||
@ -33,7 +33,10 @@ export const transformStyle: NodeTransform = (node, context) => {
|
|||||||
const listDelimiterRE = /;(?![^(]*\))/g
|
const listDelimiterRE = /;(?![^(]*\))/g
|
||||||
const propertyDelimiterRE = /:(.+)/
|
const propertyDelimiterRE = /:(.+)/
|
||||||
|
|
||||||
function parseInlineCSS(cssText: string): Record<string, string> {
|
function parseInlineCSS(
|
||||||
|
cssText: string,
|
||||||
|
loc: SourceLocation
|
||||||
|
): SimpleExpressionNode {
|
||||||
const res: Record<string, string> = {}
|
const res: Record<string, string> = {}
|
||||||
cssText.split(listDelimiterRE).forEach(item => {
|
cssText.split(listDelimiterRE).forEach(item => {
|
||||||
if (item) {
|
if (item) {
|
||||||
@ -41,5 +44,5 @@ function parseInlineCSS(cssText: string): Record<string, string> {
|
|||||||
tmp.length > 1 && (res[tmp[0].trim()] = tmp[1].trim())
|
tmp.length > 1 && (res[tmp[0].trim()] = tmp[1].trim())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return res
|
return createSimpleExpression(JSON.stringify(res), false, loc, true)
|
||||||
}
|
}
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
import { DirectiveTransform } from 'packages/compiler-core/src/transform'
|
|
||||||
|
|
||||||
export const transformCloak: DirectiveTransform = (node, context) => {
|
|
||||||
return { props: [], needRuntime: false }
|
|
||||||
}
|
|
@ -24,7 +24,6 @@ export const transformVHtml: DirectiveTransform = (dir, node, context) => {
|
|||||||
createSimpleExpression(`innerHTML`, true, loc),
|
createSimpleExpression(`innerHTML`, true, loc),
|
||||||
exp || createSimpleExpression('', true)
|
exp || createSimpleExpression('', true)
|
||||||
)
|
)
|
||||||
],
|
]
|
||||||
needRuntime: false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,8 @@ import {
|
|||||||
DirectiveTransform,
|
DirectiveTransform,
|
||||||
ElementTypes,
|
ElementTypes,
|
||||||
findProp,
|
findProp,
|
||||||
NodeTypes
|
NodeTypes,
|
||||||
|
hasDynamicKeyVBind
|
||||||
} from '@vue/compiler-core'
|
} from '@vue/compiler-core'
|
||||||
import { createDOMCompilerError, DOMErrorCodes } from '../errors'
|
import { createDOMCompilerError, DOMErrorCodes } from '../errors'
|
||||||
import {
|
import {
|
||||||
@ -16,68 +17,104 @@ import {
|
|||||||
|
|
||||||
export const transformModel: DirectiveTransform = (dir, node, context) => {
|
export const transformModel: DirectiveTransform = (dir, node, context) => {
|
||||||
const baseResult = baseTransform(dir, node, context)
|
const baseResult = baseTransform(dir, node, context)
|
||||||
// base transform has errors
|
// base transform has errors OR component v-model (only need props)
|
||||||
if (!baseResult.props.length) {
|
if (!baseResult.props.length || node.tagType === ElementTypes.COMPONENT) {
|
||||||
return baseResult
|
return baseResult
|
||||||
}
|
}
|
||||||
|
|
||||||
const { tag, tagType } = node
|
if (dir.arg) {
|
||||||
if (tagType === ElementTypes.ELEMENT) {
|
context.onError(
|
||||||
if (dir.arg) {
|
createDOMCompilerError(
|
||||||
context.onError(
|
DOMErrorCodes.X_V_MODEL_ARG_ON_ELEMENT,
|
||||||
createDOMCompilerError(
|
dir.arg.loc
|
||||||
DOMErrorCodes.X_V_MODEL_ARG_ON_ELEMENT,
|
|
||||||
dir.arg.loc
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (tag === 'input' || tag === 'textarea' || tag === 'select') {
|
function checkDuplicatedValue() {
|
||||||
let directiveToUse = V_MODEL_TEXT
|
const value = findProp(node, 'value')
|
||||||
let isInvalidType = false
|
if (value) {
|
||||||
if (tag === 'input') {
|
|
||||||
const type = findProp(node, `type`)
|
|
||||||
if (type) {
|
|
||||||
if (type.type === NodeTypes.DIRECTIVE) {
|
|
||||||
// :type="foo"
|
|
||||||
directiveToUse = V_MODEL_DYNAMIC
|
|
||||||
} else if (type.value) {
|
|
||||||
switch (type.value.content) {
|
|
||||||
case 'radio':
|
|
||||||
directiveToUse = V_MODEL_RADIO
|
|
||||||
break
|
|
||||||
case 'checkbox':
|
|
||||||
directiveToUse = V_MODEL_CHECKBOX
|
|
||||||
break
|
|
||||||
case 'file':
|
|
||||||
isInvalidType = true
|
|
||||||
context.onError(
|
|
||||||
createDOMCompilerError(
|
|
||||||
DOMErrorCodes.X_V_MODEL_ON_FILE_INPUT_ELEMENT,
|
|
||||||
dir.loc
|
|
||||||
)
|
|
||||||
)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (tag === 'select') {
|
|
||||||
directiveToUse = V_MODEL_SELECT
|
|
||||||
}
|
|
||||||
// inject runtime directive
|
|
||||||
// by returning the helper symbol via needRuntime
|
|
||||||
// the import will replaced a resolveDirective call.
|
|
||||||
if (!isInvalidType) {
|
|
||||||
baseResult.needRuntime = context.helper(directiveToUse)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
context.onError(
|
context.onError(
|
||||||
createDOMCompilerError(
|
createDOMCompilerError(
|
||||||
DOMErrorCodes.X_V_MODEL_ON_INVALID_ELEMENT,
|
DOMErrorCodes.X_V_MODEL_UNNECESSARY_VALUE,
|
||||||
dir.loc
|
value.loc
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { tag } = node
|
||||||
|
if (tag === 'input' || tag === 'textarea' || tag === 'select') {
|
||||||
|
let directiveToUse = V_MODEL_TEXT
|
||||||
|
let isInvalidType = false
|
||||||
|
if (tag === 'input') {
|
||||||
|
const type = findProp(node, `type`)
|
||||||
|
if (type) {
|
||||||
|
if (type.type === NodeTypes.DIRECTIVE) {
|
||||||
|
// :type="foo"
|
||||||
|
directiveToUse = V_MODEL_DYNAMIC
|
||||||
|
} else if (type.value) {
|
||||||
|
switch (type.value.content) {
|
||||||
|
case 'radio':
|
||||||
|
directiveToUse = V_MODEL_RADIO
|
||||||
|
break
|
||||||
|
case 'checkbox':
|
||||||
|
directiveToUse = V_MODEL_CHECKBOX
|
||||||
|
break
|
||||||
|
case 'file':
|
||||||
|
isInvalidType = true
|
||||||
|
context.onError(
|
||||||
|
createDOMCompilerError(
|
||||||
|
DOMErrorCodes.X_V_MODEL_ON_FILE_INPUT_ELEMENT,
|
||||||
|
dir.loc
|
||||||
|
)
|
||||||
|
)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
// text type
|
||||||
|
__DEV__ && checkDuplicatedValue()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (hasDynamicKeyVBind(node)) {
|
||||||
|
// element has bindings with dynamic keys, which can possibly contain
|
||||||
|
// "type".
|
||||||
|
directiveToUse = V_MODEL_DYNAMIC
|
||||||
|
} else {
|
||||||
|
// text type
|
||||||
|
__DEV__ && checkDuplicatedValue()
|
||||||
|
}
|
||||||
|
} else if (tag === 'select') {
|
||||||
|
directiveToUse = V_MODEL_SELECT
|
||||||
|
} else if (tag === 'textarea') {
|
||||||
|
__DEV__ && checkDuplicatedValue()
|
||||||
|
}
|
||||||
|
// inject runtime directive
|
||||||
|
// by returning the helper symbol via needRuntime
|
||||||
|
// the import will replaced a resolveDirective call.
|
||||||
|
if (!isInvalidType) {
|
||||||
|
baseResult.needRuntime = context.helper(directiveToUse)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
context.onError(
|
||||||
|
createDOMCompilerError(
|
||||||
|
DOMErrorCodes.X_V_MODEL_ON_INVALID_ELEMENT,
|
||||||
|
dir.loc
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// native vmodel doesn't need the `modelValue` props since they are also
|
||||||
|
// passed to the runtime as `binding.value`. removing it reduces code size.
|
||||||
|
baseResult.props = baseResult.props.filter(p => {
|
||||||
|
if (
|
||||||
|
p.key.type === NodeTypes.SIMPLE_EXPRESSION &&
|
||||||
|
p.key.content === 'modelValue'
|
||||||
|
) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
return baseResult
|
return baseResult
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,9 @@ import {
|
|||||||
createCallExpression,
|
createCallExpression,
|
||||||
createObjectExpression,
|
createObjectExpression,
|
||||||
createSimpleExpression,
|
createSimpleExpression,
|
||||||
NodeTypes
|
NodeTypes,
|
||||||
|
createCompoundExpression,
|
||||||
|
ExpressionNode
|
||||||
} from '@vue/compiler-core'
|
} from '@vue/compiler-core'
|
||||||
import { V_ON_WITH_MODIFIERS, V_ON_WITH_KEYS } from '../runtimeHelpers'
|
import { V_ON_WITH_MODIFIERS, V_ON_WITH_KEYS } from '../runtimeHelpers'
|
||||||
import { makeMap } from '@vue/shared'
|
import { makeMap } from '@vue/shared'
|
||||||
@ -52,6 +54,24 @@ const generateModifiers = (modifiers: string[]) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const transformClick = (key: ExpressionNode, event: string) => {
|
||||||
|
const isStaticClick =
|
||||||
|
key.type === NodeTypes.SIMPLE_EXPRESSION &&
|
||||||
|
key.isStatic &&
|
||||||
|
key.content.toLowerCase() === 'onclick'
|
||||||
|
return isStaticClick
|
||||||
|
? createSimpleExpression(event, true)
|
||||||
|
: key.type !== NodeTypes.SIMPLE_EXPRESSION
|
||||||
|
? createCompoundExpression([
|
||||||
|
`(`,
|
||||||
|
key,
|
||||||
|
`).toLowerCase() === "onclick" ? "${event}" : (`,
|
||||||
|
key,
|
||||||
|
`)`
|
||||||
|
])
|
||||||
|
: key
|
||||||
|
}
|
||||||
|
|
||||||
export const transformOn: DirectiveTransform = (dir, node, context) => {
|
export const transformOn: DirectiveTransform = (dir, node, context) => {
|
||||||
return baseTransform(dir, node, context, baseResult => {
|
return baseTransform(dir, node, context, baseResult => {
|
||||||
const { modifiers } = dir
|
const { modifiers } = dir
|
||||||
@ -64,6 +84,14 @@ export const transformOn: DirectiveTransform = (dir, node, context) => {
|
|||||||
eventOptionModifiers
|
eventOptionModifiers
|
||||||
} = generateModifiers(modifiers)
|
} = generateModifiers(modifiers)
|
||||||
|
|
||||||
|
// normalize click.right and click.middle since they don't actually fire
|
||||||
|
if (nonKeyModifiers.includes('right')) {
|
||||||
|
key = transformClick(key, `onContextmenu`)
|
||||||
|
}
|
||||||
|
if (nonKeyModifiers.includes('middle')) {
|
||||||
|
key = transformClick(key, `onMouseup`)
|
||||||
|
}
|
||||||
|
|
||||||
if (nonKeyModifiers.length) {
|
if (nonKeyModifiers.length) {
|
||||||
handlerExp = createCallExpression(context.helper(V_ON_WITH_MODIFIERS), [
|
handlerExp = createCallExpression(context.helper(V_ON_WITH_MODIFIERS), [
|
||||||
handlerExp,
|
handlerExp,
|
||||||
@ -102,8 +130,7 @@ export const transformOn: DirectiveTransform = (dir, node, context) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: [createObjectProperty(key, handlerExp)],
|
props: [createObjectProperty(key, handlerExp)]
|
||||||
needRuntime: false
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,6 @@ export const transformVText: DirectiveTransform = (dir, node, context) => {
|
|||||||
createSimpleExpression(`textContent`, true, loc),
|
createSimpleExpression(`textContent`, true, loc),
|
||||||
exp || createSimpleExpression('', true)
|
exp || createSimpleExpression('', true)
|
||||||
)
|
)
|
||||||
],
|
]
|
||||||
needRuntime: false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,43 @@
|
|||||||
|
import {
|
||||||
|
NodeTransform,
|
||||||
|
NodeTypes,
|
||||||
|
ElementTypes,
|
||||||
|
ComponentNode,
|
||||||
|
IfBranchNode
|
||||||
|
} from '@vue/compiler-core'
|
||||||
|
import { TRANSITION } from '../runtimeHelpers'
|
||||||
|
import { createDOMCompilerError, DOMErrorCodes } from '../errors'
|
||||||
|
|
||||||
|
export const warnTransitionChildren: NodeTransform = (node, context) => {
|
||||||
|
if (
|
||||||
|
node.type === NodeTypes.ELEMENT &&
|
||||||
|
node.tagType === ElementTypes.COMPONENT
|
||||||
|
) {
|
||||||
|
const component = context.isBuiltInComponent(node.tag)
|
||||||
|
if (component === TRANSITION) {
|
||||||
|
return () => {
|
||||||
|
if (node.children.length && hasMultipleChildren(node)) {
|
||||||
|
context.onError(
|
||||||
|
createDOMCompilerError(
|
||||||
|
DOMErrorCodes.X_TRANSITION_INVALID_CHILDREN,
|
||||||
|
{
|
||||||
|
start: node.children[0].loc.start,
|
||||||
|
end: node.children[node.children.length - 1].loc.end,
|
||||||
|
source: ''
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasMultipleChildren(node: ComponentNode | IfBranchNode): boolean {
|
||||||
|
const child = node.children[0]
|
||||||
|
return (
|
||||||
|
node.children.length !== 1 ||
|
||||||
|
child.type === NodeTypes.FOR ||
|
||||||
|
(child.type === NodeTypes.IF && child.branches.some(hasMultipleChildren))
|
||||||
|
)
|
||||||
|
}
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
exports[`source map 1`] = `
|
exports[`source map 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"mappings": ";;;;UAAA,aACE;IAAK,gCAAMA,WAAM",
|
"mappings": ";;;wBACE,aAA8B;IAAzB,aAAmB,4BAAbA,WAAM",
|
||||||
"names": Array [
|
"names": Array [
|
||||||
"render",
|
"render",
|
||||||
],
|
],
|
||||||
@ -20,8 +20,8 @@ Object {
|
|||||||
|
|
||||||
exports[`template errors 1`] = `
|
exports[`template errors 1`] = `
|
||||||
Array [
|
Array [
|
||||||
[SyntaxError: Invalid JavaScript expression. (2:13)],
|
[SyntaxError: Error parsing JavaScript expression: Unexpected token (1:3)],
|
||||||
[SyntaxError: v-bind is missing expression. (1:6)],
|
[SyntaxError: v-bind is missing expression.],
|
||||||
[SyntaxError: v-model can only be used on <input>, <textarea> and <select> elements. (2:17)],
|
[SyntaxError: v-model can only be used on <input>, <textarea> and <select> elements.],
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
@ -1,39 +1,38 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`compiler sfc: transform asset url support uri fragment 1`] = `
|
exports[`compiler sfc: transform asset url support uri fragment 1`] = `
|
||||||
"import { createVNode, createBlock, openBlock } from \\"vue\\"
|
"import { createVNode as _createVNode, openBlock as _openBlock, createBlock as _createBlock } from \\"vue\\"
|
||||||
import _imports_0 from '@svg/file.svg'
|
import _imports_0 from '@svg/file.svg'
|
||||||
|
|
||||||
|
|
||||||
const _hoisted_1 = _imports_0 + '#fragment'
|
const _hoisted_1 = _imports_0 + '#fragment'
|
||||||
|
|
||||||
export default function render() {
|
export function render(_ctx, _cache) {
|
||||||
const _ctx = this
|
return (_openBlock(), _createBlock(\\"use\\", { href: _hoisted_1 }))
|
||||||
return (openBlock(), createBlock(\\"use\\", { href: _hoisted_1 }))
|
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`compiler sfc: transform asset url support uri is empty 1`] = `
|
exports[`compiler sfc: transform asset url support uri is empty 1`] = `
|
||||||
"import { createVNode, createBlock, openBlock } from \\"vue\\"
|
"import { createVNode as _createVNode, openBlock as _openBlock, createBlock as _createBlock } from \\"vue\\"
|
||||||
|
|
||||||
export default function render() {
|
export function render(_ctx, _cache) {
|
||||||
const _ctx = this
|
return (_openBlock(), _createBlock(\\"use\\", { href: '' }))
|
||||||
return (openBlock(), createBlock(\\"use\\", { href: '' }))
|
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`compiler sfc: transform asset url transform assetUrls 1`] = `
|
exports[`compiler sfc: transform asset url transform assetUrls 1`] = `
|
||||||
"import { createVNode, createBlock, Fragment, openBlock } from \\"vue\\"
|
"import { createVNode as _createVNode, Fragment as _Fragment, openBlock as _openBlock, createBlock as _createBlock } from \\"vue\\"
|
||||||
import _imports_0 from './logo.png'
|
import _imports_0 from './logo.png'
|
||||||
import _imports_1 from 'fixtures/logo.png'
|
import _imports_1 from 'fixtures/logo.png'
|
||||||
|
|
||||||
|
|
||||||
export default function render() {
|
export function render(_ctx, _cache) {
|
||||||
const _ctx = this
|
return (_openBlock(), _createBlock(_Fragment, null, [
|
||||||
return (openBlock(), createBlock(Fragment, null, [
|
_createVNode(\\"img\\", { src: _imports_0 }),
|
||||||
createVNode(\\"img\\", { src: _imports_0 }),
|
_createVNode(\\"img\\", { src: _imports_1 }),
|
||||||
createVNode(\\"img\\", { src: _imports_1 }),
|
_createVNode(\\"img\\", { src: _imports_1 }),
|
||||||
createVNode(\\"img\\", { src: _imports_1 })
|
_createVNode(\\"img\\", { src: \\"http://example.com/fixtures/logo.png\\" }),
|
||||||
|
_createVNode(\\"img\\", { src: \\"/fixtures/logo.png\\" })
|
||||||
], 64 /* STABLE_FRAGMENT */))
|
], 64 /* STABLE_FRAGMENT */))
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`compiler sfc: transform srcset transform srcset 1`] = `
|
exports[`compiler sfc: transform srcset transform srcset 1`] = `
|
||||||
"import { createVNode, createBlock, Fragment, openBlock } from \\"vue\\"
|
"import { createVNode as _createVNode, Fragment as _Fragment, openBlock as _openBlock, createBlock as _createBlock } from \\"vue\\"
|
||||||
import _imports_0 from './logo.png'
|
import _imports_0 from './logo.png'
|
||||||
|
|
||||||
|
|
||||||
@ -12,37 +12,49 @@ const _hoisted_4 = _imports_0 + ', ' + _imports_0 + '2x'
|
|||||||
const _hoisted_5 = _imports_0 + '2x, ' + _imports_0
|
const _hoisted_5 = _imports_0 + '2x, ' + _imports_0
|
||||||
const _hoisted_6 = _imports_0 + '2x, ' + _imports_0 + '3x'
|
const _hoisted_6 = _imports_0 + '2x, ' + _imports_0 + '3x'
|
||||||
const _hoisted_7 = _imports_0 + ', ' + _imports_0 + '2x, ' + _imports_0 + '3x'
|
const _hoisted_7 = _imports_0 + ', ' + _imports_0 + '2x, ' + _imports_0 + '3x'
|
||||||
|
const _hoisted_8 = \\"/logo.png\\" + ', ' + _imports_0 + '2x'
|
||||||
|
|
||||||
export default function render() {
|
export function render(_ctx, _cache) {
|
||||||
const _ctx = this
|
return (_openBlock(), _createBlock(_Fragment, null, [
|
||||||
return (openBlock(), createBlock(Fragment, null, [
|
_createVNode(\\"img\\", {
|
||||||
createVNode(\\"img\\", {
|
|
||||||
src: \\"./logo.png\\",
|
src: \\"./logo.png\\",
|
||||||
srcset: _hoisted_1
|
srcset: _hoisted_1
|
||||||
}),
|
}),
|
||||||
createVNode(\\"img\\", {
|
_createVNode(\\"img\\", {
|
||||||
src: \\"./logo.png\\",
|
src: \\"./logo.png\\",
|
||||||
srcset: _hoisted_2
|
srcset: _hoisted_2
|
||||||
}),
|
}),
|
||||||
createVNode(\\"img\\", {
|
_createVNode(\\"img\\", {
|
||||||
src: \\"./logo.png\\",
|
src: \\"./logo.png\\",
|
||||||
srcset: _hoisted_3
|
srcset: _hoisted_3
|
||||||
}),
|
}),
|
||||||
createVNode(\\"img\\", {
|
_createVNode(\\"img\\", {
|
||||||
src: \\"./logo.png\\",
|
src: \\"./logo.png\\",
|
||||||
srcset: _hoisted_4
|
srcset: _hoisted_4
|
||||||
}),
|
}),
|
||||||
createVNode(\\"img\\", {
|
_createVNode(\\"img\\", {
|
||||||
src: \\"./logo.png\\",
|
src: \\"./logo.png\\",
|
||||||
srcset: _hoisted_5
|
srcset: _hoisted_5
|
||||||
}),
|
}),
|
||||||
createVNode(\\"img\\", {
|
_createVNode(\\"img\\", {
|
||||||
src: \\"./logo.png\\",
|
src: \\"./logo.png\\",
|
||||||
srcset: _hoisted_6
|
srcset: _hoisted_6
|
||||||
}),
|
}),
|
||||||
createVNode(\\"img\\", {
|
_createVNode(\\"img\\", {
|
||||||
src: \\"./logo.png\\",
|
src: \\"./logo.png\\",
|
||||||
srcset: _hoisted_7
|
srcset: _hoisted_7
|
||||||
|
}),
|
||||||
|
_createVNode(\\"img\\", {
|
||||||
|
src: \\"/logo.png\\",
|
||||||
|
srcset: \\"/logo.png, /logo.png 2x\\"
|
||||||
|
}),
|
||||||
|
_createVNode(\\"img\\", {
|
||||||
|
src: \\"https://example.com/logo.png\\",
|
||||||
|
srcset: \\"https://example.com/logo.png, https://example.com/logo.png 2x\\"
|
||||||
|
}),
|
||||||
|
_createVNode(\\"img\\", {
|
||||||
|
src: \\"/logo.png\\",
|
||||||
|
srcset: _hoisted_8
|
||||||
})
|
})
|
||||||
], 64 /* STABLE_FRAGMENT */))
|
], 64 /* STABLE_FRAGMENT */))
|
||||||
}"
|
}"
|
||||||
|
237
packages/compiler-sfc/__tests__/compileStyle.spec.ts
Normal file
237
packages/compiler-sfc/__tests__/compileStyle.spec.ts
Normal file
@ -0,0 +1,237 @@
|
|||||||
|
import { compileStyle } from '../src/compileStyle'
|
||||||
|
import { mockWarn } from '@vue/shared'
|
||||||
|
|
||||||
|
function compile(source: string): string {
|
||||||
|
const res = compileStyle({
|
||||||
|
source,
|
||||||
|
filename: 'test.css',
|
||||||
|
id: 'test'
|
||||||
|
})
|
||||||
|
if (res.errors.length) {
|
||||||
|
res.errors.forEach(err => {
|
||||||
|
console.error(err)
|
||||||
|
})
|
||||||
|
expect(res.errors.length).toBe(0)
|
||||||
|
}
|
||||||
|
return res.code
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('SFC scoped CSS', () => {
|
||||||
|
mockWarn()
|
||||||
|
|
||||||
|
test('simple selectors', () => {
|
||||||
|
expect(compile(`h1 { color: red; }`)).toMatch(`h1[test] { color: red;`)
|
||||||
|
expect(compile(`.foo { color: red; }`)).toMatch(`.foo[test] { color: red;`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('descendent selector', () => {
|
||||||
|
expect(compile(`h1 .foo { color: red; }`)).toMatch(
|
||||||
|
`h1 .foo[test] { color: red;`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('multiple selectors', () => {
|
||||||
|
expect(compile(`h1 .foo, .bar, .baz { color: red; }`)).toMatch(
|
||||||
|
`h1 .foo[test], .bar[test], .baz[test] { color: red;`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('pseudo class', () => {
|
||||||
|
expect(compile(`.foo:after { color: red; }`)).toMatch(
|
||||||
|
`.foo[test]:after { color: red;`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('pseudo element', () => {
|
||||||
|
expect(compile(`::selection { display: none; }`)).toMatch(
|
||||||
|
'[test]::selection {'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('spaces before pseudo element', () => {
|
||||||
|
const code = compile(`.abc, ::selection { color: red; }`)
|
||||||
|
expect(code).toMatch('.abc[test],')
|
||||||
|
expect(code).toMatch('[test]::selection {')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('::v-deep', () => {
|
||||||
|
expect(compile(`::v-deep(.foo) { color: red; }`)).toMatchInlineSnapshot(`
|
||||||
|
"[test] .foo { color: red;
|
||||||
|
}"
|
||||||
|
`)
|
||||||
|
expect(compile(`::v-deep(.foo .bar) { color: red; }`))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"[test] .foo .bar { color: red;
|
||||||
|
}"
|
||||||
|
`)
|
||||||
|
expect(compile(`.baz .qux ::v-deep(.foo .bar) { color: red; }`))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
".baz .qux[test] .foo .bar { color: red;
|
||||||
|
}"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('::v-slotted', () => {
|
||||||
|
expect(compile(`::v-slotted(.foo) { color: red; }`)).toMatchInlineSnapshot(`
|
||||||
|
".foo[test-s] { color: red;
|
||||||
|
}"
|
||||||
|
`)
|
||||||
|
expect(compile(`::v-slotted(.foo .bar) { color: red; }`))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
".foo .bar[test-s] { color: red;
|
||||||
|
}"
|
||||||
|
`)
|
||||||
|
expect(compile(`.baz .qux ::v-slotted(.foo .bar) { color: red; }`))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
".baz .qux .foo .bar[test-s] { color: red;
|
||||||
|
}"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('::v-global', () => {
|
||||||
|
expect(compile(`::v-global(.foo) { color: red; }`)).toMatchInlineSnapshot(`
|
||||||
|
".foo { color: red;
|
||||||
|
}"
|
||||||
|
`)
|
||||||
|
expect(compile(`::v-global(.foo .bar) { color: red; }`))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
".foo .bar { color: red;
|
||||||
|
}"
|
||||||
|
`)
|
||||||
|
// global ignores anything before it
|
||||||
|
expect(compile(`.baz .qux ::v-global(.foo .bar) { color: red; }`))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
".foo .bar { color: red;
|
||||||
|
}"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('media query', () => {
|
||||||
|
expect(compile(`@media print { .foo { color: red }}`))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"@media print {
|
||||||
|
.foo[test] { color: red
|
||||||
|
}}"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('supports query', () => {
|
||||||
|
expect(compile(`@supports(display: grid) { .foo { display: grid }}`))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"@supports(display: grid) {
|
||||||
|
.foo[test] { display: grid
|
||||||
|
}}"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('scoped keyframes', () => {
|
||||||
|
const style = compile(`
|
||||||
|
.anim {
|
||||||
|
animation: color 5s infinite, other 5s;
|
||||||
|
}
|
||||||
|
.anim-2 {
|
||||||
|
animation-name: color;
|
||||||
|
animation-duration: 5s;
|
||||||
|
}
|
||||||
|
.anim-3 {
|
||||||
|
animation: 5s color infinite, 5s other;
|
||||||
|
}
|
||||||
|
.anim-multiple {
|
||||||
|
animation: color 5s infinite, opacity 2s;
|
||||||
|
}
|
||||||
|
.anim-multiple-2 {
|
||||||
|
animation-name: color, opacity;
|
||||||
|
animation-duration: 5s, 2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes color {
|
||||||
|
from { color: red; }
|
||||||
|
to { color: green; }
|
||||||
|
}
|
||||||
|
@-webkit-keyframes color {
|
||||||
|
from { color: red; }
|
||||||
|
to { color: green; }
|
||||||
|
}
|
||||||
|
@keyframes opacity {
|
||||||
|
from { opacity: 0; }
|
||||||
|
to { opacity: 1; }
|
||||||
|
}
|
||||||
|
@-webkit-keyframes opacity {
|
||||||
|
from { opacity: 0; }
|
||||||
|
to { opacity: 1; }
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
expect(style).toContain(
|
||||||
|
`.anim[test] {\n animation: color-test 5s infinite, other 5s;`
|
||||||
|
)
|
||||||
|
expect(style).toContain(`.anim-2[test] {\n animation-name: color-test`)
|
||||||
|
expect(style).toContain(
|
||||||
|
`.anim-3[test] {\n animation: 5s color-test infinite, 5s other;`
|
||||||
|
)
|
||||||
|
expect(style).toContain(`@keyframes color-test {`)
|
||||||
|
expect(style).toContain(`@-webkit-keyframes color-test {`)
|
||||||
|
|
||||||
|
expect(style).toContain(
|
||||||
|
`.anim-multiple[test] {\n animation: color-test 5s infinite,opacity-test 2s;`
|
||||||
|
)
|
||||||
|
expect(style).toContain(
|
||||||
|
`.anim-multiple-2[test] {\n animation-name: color-test,opacity-test;`
|
||||||
|
)
|
||||||
|
expect(style).toContain(`@keyframes opacity-test {`)
|
||||||
|
expect(style).toContain(`@-webkit-keyframes opacity-test {`)
|
||||||
|
})
|
||||||
|
|
||||||
|
// vue-loader/#1370
|
||||||
|
test('spaces after selector', () => {
|
||||||
|
const { code } = compileStyle({
|
||||||
|
source: `.foo , .bar { color: red; }`,
|
||||||
|
filename: 'test.css',
|
||||||
|
id: 'test'
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(code).toMatchInlineSnapshot(`
|
||||||
|
".foo[test], .bar[test] { color: red;
|
||||||
|
}"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('deprecated syntax', () => {
|
||||||
|
test('::v-deep as combinator', () => {
|
||||||
|
expect(compile(`::v-deep .foo { color: red; }`)).toMatchInlineSnapshot(`
|
||||||
|
"[test] .foo { color: red;
|
||||||
|
}"
|
||||||
|
`)
|
||||||
|
expect(compile(`.bar ::v-deep .foo { color: red; }`))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
".bar[test] .foo { color: red;
|
||||||
|
}"
|
||||||
|
`)
|
||||||
|
expect(
|
||||||
|
`::v-deep usage as a combinator has been deprecated.`
|
||||||
|
).toHaveBeenWarned()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('>>> (deprecated syntax)', () => {
|
||||||
|
const code = compile(`>>> .foo { color: red; }`)
|
||||||
|
expect(code).toMatchInlineSnapshot(`
|
||||||
|
"[test] .foo { color: red;
|
||||||
|
}"
|
||||||
|
`)
|
||||||
|
expect(
|
||||||
|
`the >>> and /deep/ combinators have been deprecated.`
|
||||||
|
).toHaveBeenWarned()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('/deep/ (deprecated syntax)', () => {
|
||||||
|
const code = compile(`/deep/ .foo { color: red; }`)
|
||||||
|
expect(code).toMatchInlineSnapshot(`
|
||||||
|
"[test] .foo { color: red;
|
||||||
|
}"
|
||||||
|
`)
|
||||||
|
expect(
|
||||||
|
`the >>> and /deep/ combinators have been deprecated.`
|
||||||
|
).toHaveBeenWarned()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
@ -9,7 +9,7 @@ test('should work', () => {
|
|||||||
expect(result.errors.length).toBe(0)
|
expect(result.errors.length).toBe(0)
|
||||||
expect(result.source).toBe(source)
|
expect(result.source).toBe(source)
|
||||||
// should expose render fn
|
// should expose render fn
|
||||||
expect(result.code).toMatch(`export default function render()`)
|
expect(result.code).toMatch(`export function render(`)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('preprocess pug', () => {
|
test('preprocess pug', () => {
|
||||||
@ -23,7 +23,7 @@ body
|
|||||||
</template>
|
</template>
|
||||||
`,
|
`,
|
||||||
{ filename: 'example.vue', sourceMap: true }
|
{ filename: 'example.vue', sourceMap: true }
|
||||||
).template as SFCTemplateBlock
|
).descriptor.template as SFCTemplateBlock
|
||||||
|
|
||||||
const result = compileTemplate({
|
const result = compileTemplate({
|
||||||
filename: 'example.vue',
|
filename: 'example.vue',
|
||||||
@ -35,10 +35,10 @@ body
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('warn missing preprocessor', () => {
|
test('warn missing preprocessor', () => {
|
||||||
const template = parse(`<template lang="unknownLang">\n</template>\n`, {
|
const template = parse(`<template lang="unknownLang">hi</template>\n`, {
|
||||||
filename: 'example.vue',
|
filename: 'example.vue',
|
||||||
sourceMap: true
|
sourceMap: true
|
||||||
}).template as SFCTemplateBlock
|
}).descriptor.template as SFCTemplateBlock
|
||||||
|
|
||||||
const result = compileTemplate({
|
const result = compileTemplate({
|
||||||
filename: 'example.vue',
|
filename: 'example.vue',
|
||||||
@ -50,7 +50,7 @@ test('warn missing preprocessor', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('transform asset url options', () => {
|
test('transform asset url options', () => {
|
||||||
const input = { source: `<foo bar="baz"/>`, filename: 'example.vue' }
|
const input = { source: `<foo bar="~baz"/>`, filename: 'example.vue' }
|
||||||
// Object option
|
// Object option
|
||||||
const { code: code1 } = compileTemplate({
|
const { code: code1 } = compileTemplate({
|
||||||
...input,
|
...input,
|
||||||
@ -73,7 +73,7 @@ test('source map', () => {
|
|||||||
</template>
|
</template>
|
||||||
`,
|
`,
|
||||||
{ filename: 'example.vue', sourceMap: true }
|
{ filename: 'example.vue', sourceMap: true }
|
||||||
).template as SFCTemplateBlock
|
).descriptor.template as SFCTemplateBlock
|
||||||
|
|
||||||
const result = compileTemplate({
|
const result = compileTemplate({
|
||||||
filename: 'example.vue',
|
filename: 'example.vue',
|
||||||
@ -100,7 +100,7 @@ test('preprocessor errors', () => {
|
|||||||
</template>
|
</template>
|
||||||
`,
|
`,
|
||||||
{ filename: 'example.vue', sourceMap: true }
|
{ filename: 'example.vue', sourceMap: true }
|
||||||
).template as SFCTemplateBlock
|
).descriptor.template as SFCTemplateBlock
|
||||||
|
|
||||||
const result = compileTemplate({
|
const result = compileTemplate({
|
||||||
filename: 'example.vue',
|
filename: 'example.vue',
|
||||||
|
@ -1,21 +1,40 @@
|
|||||||
import { parse } from '../src'
|
import { parse } from '../src'
|
||||||
import { mockWarn } from '@vue/runtime-test'
|
import { mockWarn } from '@vue/shared'
|
||||||
|
import { baseParse, baseCompile } from '@vue/compiler-core'
|
||||||
|
import { SourceMapConsumer } from 'source-map'
|
||||||
|
|
||||||
describe('compiler:sfc', () => {
|
describe('compiler:sfc', () => {
|
||||||
mockWarn()
|
mockWarn()
|
||||||
|
|
||||||
describe('source map', () => {
|
describe('source map', () => {
|
||||||
test('style block', () => {
|
test('style block', () => {
|
||||||
const style = parse(`<style>\n.color {\n color: red;\n }\n</style>\n`)
|
// Padding determines how many blank lines will there be before the style block
|
||||||
.styles[0]
|
const padding = Math.round(Math.random() * 10)
|
||||||
// TODO need to actually test this with SourceMapConsumer
|
const style = parse(
|
||||||
|
`${'\n'.repeat(padding)}<style>\n.color {\n color: red;\n }\n</style>\n`
|
||||||
|
).descriptor.styles[0]
|
||||||
|
|
||||||
expect(style.map).not.toBeUndefined()
|
expect(style.map).not.toBeUndefined()
|
||||||
|
|
||||||
|
const consumer = new SourceMapConsumer(style.map!)
|
||||||
|
consumer.eachMapping(mapping => {
|
||||||
|
expect(mapping.originalLine - mapping.generatedLine).toBe(padding)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test('script block', () => {
|
test('script block', () => {
|
||||||
const script = parse(`<script>\nconsole.log(1)\n }\n</script>\n`).script
|
// Padding determines how many blank lines will there be before the style block
|
||||||
// TODO need to actually test this with SourceMapConsumer
|
const padding = Math.round(Math.random() * 10)
|
||||||
|
const script = parse(
|
||||||
|
`${'\n'.repeat(padding)}<script>\nconsole.log(1)\n }\n</script>\n`
|
||||||
|
).descriptor.script
|
||||||
|
|
||||||
expect(script!.map).not.toBeUndefined()
|
expect(script!.map).not.toBeUndefined()
|
||||||
|
|
||||||
|
const consumer = new SourceMapConsumer(script!.map!)
|
||||||
|
consumer.eachMapping(mapping => {
|
||||||
|
expect(mapping.originalLine - mapping.generatedLine).toBe(padding)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -30,12 +49,12 @@ export default {}
|
|||||||
<style>
|
<style>
|
||||||
h1 { color: red }
|
h1 { color: red }
|
||||||
</style>`
|
</style>`
|
||||||
const padFalse = parse(content.trim(), { pad: false })
|
const padFalse = parse(content.trim(), { pad: false }).descriptor
|
||||||
expect(padFalse.template!.content).toBe('\n<div></div>\n')
|
expect(padFalse.template!.content).toBe('\n<div></div>\n')
|
||||||
expect(padFalse.script!.content).toBe('\nexport default {}\n')
|
expect(padFalse.script!.content).toBe('\nexport default {}\n')
|
||||||
expect(padFalse.styles[0].content).toBe('\nh1 { color: red }\n')
|
expect(padFalse.styles[0].content).toBe('\nh1 { color: red }\n')
|
||||||
|
|
||||||
const padTrue = parse(content.trim(), { pad: true })
|
const padTrue = parse(content.trim(), { pad: true }).descriptor
|
||||||
expect(padTrue.script!.content).toBe(
|
expect(padTrue.script!.content).toBe(
|
||||||
Array(3 + 1).join('//\n') + '\nexport default {}\n'
|
Array(3 + 1).join('//\n') + '\nexport default {}\n'
|
||||||
)
|
)
|
||||||
@ -43,7 +62,7 @@ h1 { color: red }
|
|||||||
Array(6 + 1).join('\n') + '\nh1 { color: red }\n'
|
Array(6 + 1).join('\n') + '\nh1 { color: red }\n'
|
||||||
)
|
)
|
||||||
|
|
||||||
const padLine = parse(content.trim(), { pad: 'line' })
|
const padLine = parse(content.trim(), { pad: 'line' }).descriptor
|
||||||
expect(padLine.script!.content).toBe(
|
expect(padLine.script!.content).toBe(
|
||||||
Array(3 + 1).join('//\n') + '\nexport default {}\n'
|
Array(3 + 1).join('//\n') + '\nexport default {}\n'
|
||||||
)
|
)
|
||||||
@ -51,7 +70,7 @@ h1 { color: red }
|
|||||||
Array(6 + 1).join('\n') + '\nh1 { color: red }\n'
|
Array(6 + 1).join('\n') + '\nh1 { color: red }\n'
|
||||||
)
|
)
|
||||||
|
|
||||||
const padSpace = parse(content.trim(), { pad: 'space' })
|
const padSpace = parse(content.trim(), { pad: 'space' }).descriptor
|
||||||
expect(padSpace.script!.content).toBe(
|
expect(padSpace.script!.content).toBe(
|
||||||
`<template>\n<div></div>\n</template>\n<script>`.replace(/./g, ' ') +
|
`<template>\n<div></div>\n</template>\n<script>`.replace(/./g, ' ') +
|
||||||
'\nexport default {}\n'
|
'\nexport default {}\n'
|
||||||
@ -65,13 +84,55 @@ h1 { color: red }
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('should ignore nodes with no content', () => {
|
test('should ignore nodes with no content', () => {
|
||||||
expect(parse(`<template/>`).template).toBe(null)
|
expect(parse(`<template/>`).descriptor.template).toBe(null)
|
||||||
expect(parse(`<script/>`).script).toBe(null)
|
expect(parse(`<script/>`).descriptor.script).toBe(null)
|
||||||
expect(parse(`<style/>`).styles.length).toBe(0)
|
expect(parse(`<style/>`).descriptor.styles.length).toBe(0)
|
||||||
expect(parse(`<custom/>`).customBlocks.length).toBe(0)
|
expect(parse(`<custom/>`).descriptor.customBlocks.length).toBe(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('error', () => {
|
test('handle empty nodes with src attribute', () => {
|
||||||
|
const { descriptor } = parse(`<script src="com"/>`)
|
||||||
|
expect(descriptor.script).toBeTruthy()
|
||||||
|
expect(descriptor.script!.content).toBeFalsy()
|
||||||
|
expect(descriptor.script!.attrs['src']).toBe('com')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('nested templates', () => {
|
||||||
|
const content = `
|
||||||
|
<template v-if="ok">ok</template>
|
||||||
|
<div><div></div></div>
|
||||||
|
`
|
||||||
|
const { descriptor } = parse(`<template>${content}</template>`)
|
||||||
|
expect(descriptor.template!.content).toBe(content)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('error tolerance', () => {
|
||||||
|
const { errors } = parse(`<template>`)
|
||||||
|
expect(errors.length).toBe(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should parse as DOM by default', () => {
|
||||||
|
const { errors } = parse(`<template><input></template>`)
|
||||||
|
expect(errors.length).toBe(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('custom compiler', () => {
|
||||||
|
const { errors } = parse(`<template><input></template>`, {
|
||||||
|
compiler: {
|
||||||
|
parse: baseParse,
|
||||||
|
compile: baseCompile
|
||||||
|
}
|
||||||
|
})
|
||||||
|
expect(errors.length).toBe(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('treat custom blocks as raw text', () => {
|
||||||
|
const { errors, descriptor } = parse(`<foo> <-& </foo>`)
|
||||||
|
expect(errors.length).toBe(0)
|
||||||
|
expect(descriptor.customBlocks[0].content).toBe(` <-& `)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('warnings', () => {
|
||||||
test('should only allow single template element', () => {
|
test('should only allow single template element', () => {
|
||||||
parse(`<template><div/></template><template><div/></template>`)
|
parse(`<template><div/></template><template><div/></template>`)
|
||||||
expect(
|
expect(
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { generate, parse, transform } from '@vue/compiler-core'
|
import { generate, baseParse, transform } from '@vue/compiler-core'
|
||||||
import { transformAssetUrl } from '../src/templateTransformAssetUrl'
|
import { transformAssetUrl } from '../src/templateTransformAssetUrl'
|
||||||
import { transformElement } from '../../compiler-core/src/transforms/transformElement'
|
import { transformElement } from '../../compiler-core/src/transforms/transformElement'
|
||||||
import { transformBind } from '../../compiler-core/src/transforms/vBind'
|
import { transformBind } from '../../compiler-core/src/transforms/vBind'
|
||||||
|
|
||||||
function compileWithAssetUrls(template: string) {
|
function compileWithAssetUrls(template: string) {
|
||||||
const ast = parse(template)
|
const ast = baseParse(template)
|
||||||
transform(ast, {
|
transform(ast, {
|
||||||
nodeTransforms: [transformAssetUrl, transformElement],
|
nodeTransforms: [transformAssetUrl, transformElement],
|
||||||
directiveTransforms: {
|
directiveTransforms: {
|
||||||
@ -20,6 +20,8 @@ describe('compiler sfc: transform asset url', () => {
|
|||||||
<img src="./logo.png"/>
|
<img src="./logo.png"/>
|
||||||
<img src="~fixtures/logo.png"/>
|
<img src="~fixtures/logo.png"/>
|
||||||
<img src="~/fixtures/logo.png"/>
|
<img src="~/fixtures/logo.png"/>
|
||||||
|
<img src="http://example.com/fixtures/logo.png"/>
|
||||||
|
<img src="/fixtures/logo.png"/>
|
||||||
`)
|
`)
|
||||||
|
|
||||||
expect(result.code).toMatchSnapshot()
|
expect(result.code).toMatchSnapshot()
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { generate, parse, transform } from '@vue/compiler-core'
|
import { generate, baseParse, transform } from '@vue/compiler-core'
|
||||||
import { transformSrcset } from '../src/templateTransformSrcset'
|
import { transformSrcset } from '../src/templateTransformSrcset'
|
||||||
import { transformElement } from '../../compiler-core/src/transforms/transformElement'
|
import { transformElement } from '../../compiler-core/src/transforms/transformElement'
|
||||||
import { transformBind } from '../../compiler-core/src/transforms/vBind'
|
import { transformBind } from '../../compiler-core/src/transforms/vBind'
|
||||||
|
|
||||||
function compileWithSrcset(template: string) {
|
function compileWithSrcset(template: string) {
|
||||||
const ast = parse(template)
|
const ast = baseParse(template)
|
||||||
transform(ast, {
|
transform(ast, {
|
||||||
nodeTransforms: [transformSrcset, transformElement],
|
nodeTransforms: [transformSrcset, transformElement],
|
||||||
directiveTransforms: {
|
directiveTransforms: {
|
||||||
@ -24,6 +24,9 @@ describe('compiler sfc: transform srcset', () => {
|
|||||||
<img src="./logo.png" srcset="./logo.png 2x, ./logo.png"/>
|
<img src="./logo.png" srcset="./logo.png 2x, ./logo.png"/>
|
||||||
<img src="./logo.png" srcset="./logo.png 2x, ./logo.png 3x"/>
|
<img src="./logo.png" srcset="./logo.png 2x, ./logo.png 3x"/>
|
||||||
<img src="./logo.png" srcset="./logo.png, ./logo.png 2x, ./logo.png 3x"/>
|
<img src="./logo.png" srcset="./logo.png, ./logo.png 2x, ./logo.png 3x"/>
|
||||||
|
<img src="/logo.png" srcset="/logo.png, /logo.png 2x"/>
|
||||||
|
<img src="https://example.com/logo.png" srcset="https://example.com/logo.png, https://example.com/logo.png 2x"/>
|
||||||
|
<img src="/logo.png" srcset="/logo.png, ./logo.png 2x"/>
|
||||||
`)
|
`)
|
||||||
|
|
||||||
expect(result.code).toMatchSnapshot()
|
expect(result.code).toMatchSnapshot()
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@vue/compiler-sfc",
|
"name": "@vue/compiler-sfc",
|
||||||
"version": "3.0.0-alpha.0",
|
"version": "3.0.0-alpha.11",
|
||||||
"description": "@vue/compiler-sfc",
|
"description": "@vue/compiler-sfc",
|
||||||
"main": "dist/compiler-sfc.cjs.js",
|
"main": "dist/compiler-sfc.cjs.js",
|
||||||
|
"types": "dist/compiler-sfc.d.ts",
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist"
|
||||||
],
|
],
|
||||||
"types": "dist/compiler-sfc.d.ts",
|
|
||||||
"buildOptions": {
|
"buildOptions": {
|
||||||
"prod": false,
|
"prod": false,
|
||||||
"formats": [
|
"formats": [
|
||||||
@ -15,7 +15,7 @@
|
|||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/vuejs/vue.git"
|
"url": "git+https://github.com/vuejs/vue-next.git"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"vue"
|
"vue"
|
||||||
@ -23,19 +23,24 @@
|
|||||||
"author": "Evan You",
|
"author": "Evan You",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/vuejs/vue/issues"
|
"url": "https://github.com/vuejs/vue-next/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/vuejs/vue-next/tree/master/packages/compiler-sfc#readme",
|
||||||
|
"peerDependencies": {
|
||||||
|
"vue": "3.0.0-alpha.11"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/vuejs/vue/tree/dev/packages/compiler-sfc#readme",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/compiler-core": "3.0.0-alpha.0",
|
"@vue/shared": "3.0.0-alpha.11",
|
||||||
"@vue/compiler-dom": "3.0.0-alpha.0",
|
"@vue/compiler-core": "3.0.0-alpha.11",
|
||||||
|
"@vue/compiler-dom": "3.0.0-alpha.11",
|
||||||
|
"@vue/compiler-ssr": "3.0.0-alpha.11",
|
||||||
"consolidate": "^0.15.1",
|
"consolidate": "^0.15.1",
|
||||||
"hash-sum": "^2.0.0",
|
"hash-sum": "^2.0.0",
|
||||||
"lru-cache": "^5.1.1",
|
"lru-cache": "^5.1.1",
|
||||||
"merge-source-map": "^1.1.0",
|
"merge-source-map": "^1.1.0",
|
||||||
"postcss": "^7.0.21",
|
"postcss": "^7.0.21",
|
||||||
"postcss-selector-parser": "^6.0.2",
|
"postcss-selector-parser": "^6.0.2",
|
||||||
"source-map": "^0.7.3"
|
"source-map": "^0.6.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/consolidate": "^0.14.0",
|
"@types/consolidate": "^0.14.0",
|
||||||
|
@ -10,7 +10,7 @@ import {
|
|||||||
} from './stylePreprocessors'
|
} from './stylePreprocessors'
|
||||||
import { RawSourceMap } from 'source-map'
|
import { RawSourceMap } from 'source-map'
|
||||||
|
|
||||||
export interface StyleCompileOptions {
|
export interface SFCStyleCompileOptions {
|
||||||
source: string
|
source: string
|
||||||
filename: string
|
filename: string
|
||||||
id: string
|
id: string
|
||||||
@ -23,11 +23,11 @@ export interface StyleCompileOptions {
|
|||||||
postcssPlugins?: any[]
|
postcssPlugins?: any[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AsyncStyleCompileOptions extends StyleCompileOptions {
|
export interface SFCAsyncStyleCompileOptions extends SFCStyleCompileOptions {
|
||||||
isAsync?: boolean
|
isAsync?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StyleCompileResults {
|
export interface SFCStyleCompileResults {
|
||||||
code: string
|
code: string
|
||||||
map: RawSourceMap | undefined
|
map: RawSourceMap | undefined
|
||||||
rawResult: LazyResult | Result | undefined
|
rawResult: LazyResult | Result | undefined
|
||||||
@ -35,22 +35,25 @@ export interface StyleCompileResults {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function compileStyle(
|
export function compileStyle(
|
||||||
options: StyleCompileOptions
|
options: SFCStyleCompileOptions
|
||||||
): StyleCompileResults {
|
): SFCStyleCompileResults {
|
||||||
return doCompileStyle({ ...options, isAsync: false }) as StyleCompileResults
|
return doCompileStyle({
|
||||||
|
...options,
|
||||||
|
isAsync: false
|
||||||
|
}) as SFCStyleCompileResults
|
||||||
}
|
}
|
||||||
|
|
||||||
export function compileStyleAsync(
|
export function compileStyleAsync(
|
||||||
options: StyleCompileOptions
|
options: SFCStyleCompileOptions
|
||||||
): Promise<StyleCompileResults> {
|
): Promise<SFCStyleCompileResults> {
|
||||||
return doCompileStyle({ ...options, isAsync: true }) as Promise<
|
return doCompileStyle({ ...options, isAsync: true }) as Promise<
|
||||||
StyleCompileResults
|
SFCStyleCompileResults
|
||||||
>
|
>
|
||||||
}
|
}
|
||||||
|
|
||||||
export function doCompileStyle(
|
export function doCompileStyle(
|
||||||
options: AsyncStyleCompileOptions
|
options: SFCAsyncStyleCompileOptions
|
||||||
): StyleCompileResults | Promise<StyleCompileResults> {
|
): SFCStyleCompileResults | Promise<SFCStyleCompileResults> {
|
||||||
const {
|
const {
|
||||||
filename,
|
filename,
|
||||||
id,
|
id,
|
||||||
@ -131,7 +134,7 @@ export function doCompileStyle(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function preprocess(
|
function preprocess(
|
||||||
options: StyleCompileOptions,
|
options: SFCStyleCompileOptions,
|
||||||
preprocessor: StylePreprocessor
|
preprocessor: StylePreprocessor
|
||||||
): StylePreprocessorResults {
|
): StylePreprocessorResults {
|
||||||
return preprocessor.render(options.source, options.map, {
|
return preprocessor.render(options.source, options.map, {
|
||||||
|
@ -2,9 +2,11 @@ import {
|
|||||||
CompilerOptions,
|
CompilerOptions,
|
||||||
CodegenResult,
|
CodegenResult,
|
||||||
CompilerError,
|
CompilerError,
|
||||||
NodeTransform
|
NodeTransform,
|
||||||
|
ParserOptions,
|
||||||
|
RootNode
|
||||||
} from '@vue/compiler-core'
|
} from '@vue/compiler-core'
|
||||||
import { RawSourceMap } from 'source-map'
|
import { SourceMapConsumer, SourceMapGenerator, RawSourceMap } from 'source-map'
|
||||||
import {
|
import {
|
||||||
transformAssetUrl,
|
transformAssetUrl,
|
||||||
AssetURLOptions,
|
AssetURLOptions,
|
||||||
@ -14,7 +16,12 @@ import { transformSrcset } from './templateTransformSrcset'
|
|||||||
import { isObject } from '@vue/shared'
|
import { isObject } from '@vue/shared'
|
||||||
import consolidate from 'consolidate'
|
import consolidate from 'consolidate'
|
||||||
|
|
||||||
export interface TemplateCompileResults {
|
export interface TemplateCompiler {
|
||||||
|
compile(template: string, options: CompilerOptions): CodegenResult
|
||||||
|
parse(template: string, options: ParserOptions): RootNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SFCTemplateCompileResults {
|
||||||
code: string
|
code: string
|
||||||
source: string
|
source: string
|
||||||
tips: string[]
|
tips: string[]
|
||||||
@ -22,13 +29,11 @@ export interface TemplateCompileResults {
|
|||||||
map?: RawSourceMap
|
map?: RawSourceMap
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TemplateCompiler {
|
export interface SFCTemplateCompileOptions {
|
||||||
compile(template: string, options: CompilerOptions): CodegenResult
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TemplateCompileOptions {
|
|
||||||
source: string
|
source: string
|
||||||
filename: string
|
filename: string
|
||||||
|
ssr?: boolean
|
||||||
|
inMap?: RawSourceMap
|
||||||
compiler?: TemplateCompiler
|
compiler?: TemplateCompiler
|
||||||
compilerOptions?: CompilerOptions
|
compilerOptions?: CompilerOptions
|
||||||
preprocessLang?: string
|
preprocessLang?: string
|
||||||
@ -37,7 +42,7 @@ export interface TemplateCompileOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function preprocess(
|
function preprocess(
|
||||||
{ source, filename, preprocessOptions }: TemplateCompileOptions,
|
{ source, filename, preprocessOptions }: SFCTemplateCompileOptions,
|
||||||
preprocessor: any
|
preprocessor: any
|
||||||
): string {
|
): string {
|
||||||
// Consolidate exposes a callback based API, but the callback is in fact
|
// Consolidate exposes a callback based API, but the callback is in fact
|
||||||
@ -59,8 +64,8 @@ function preprocess(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function compileTemplate(
|
export function compileTemplate(
|
||||||
options: TemplateCompileOptions
|
options: SFCTemplateCompileOptions
|
||||||
): TemplateCompileResults {
|
): SFCTemplateCompileResults {
|
||||||
const { preprocessLang } = options
|
const { preprocessLang } = options
|
||||||
const preprocessor =
|
const preprocessor =
|
||||||
preprocessLang && consolidate[preprocessLang as keyof typeof consolidate]
|
preprocessLang && consolidate[preprocessLang as keyof typeof consolidate]
|
||||||
@ -100,11 +105,13 @@ export function compileTemplate(
|
|||||||
|
|
||||||
function doCompileTemplate({
|
function doCompileTemplate({
|
||||||
filename,
|
filename,
|
||||||
|
inMap,
|
||||||
source,
|
source,
|
||||||
compiler = require('@vue/compiler-dom'),
|
ssr = false,
|
||||||
|
compiler = ssr ? require('@vue/compiler-ssr') : require('@vue/compiler-dom'),
|
||||||
compilerOptions = {},
|
compilerOptions = {},
|
||||||
transformAssetUrls
|
transformAssetUrls
|
||||||
}: TemplateCompileOptions): TemplateCompileResults {
|
}: SFCTemplateCompileOptions): SFCTemplateCompileResults {
|
||||||
const errors: CompilerError[] = []
|
const errors: CompilerError[] = []
|
||||||
|
|
||||||
let nodeTransforms: NodeTransform[] = []
|
let nodeTransforms: NodeTransform[] = []
|
||||||
@ -117,7 +124,7 @@ function doCompileTemplate({
|
|||||||
nodeTransforms = [transformAssetUrl, transformSrcset]
|
nodeTransforms = [transformAssetUrl, transformSrcset]
|
||||||
}
|
}
|
||||||
|
|
||||||
const { code, map } = compiler.compile(source, {
|
let { code, map } = compiler.compile(source, {
|
||||||
mode: 'module',
|
mode: 'module',
|
||||||
prefixIdentifiers: true,
|
prefixIdentifiers: true,
|
||||||
hoistStatic: true,
|
hoistStatic: true,
|
||||||
@ -128,5 +135,91 @@ function doCompileTemplate({
|
|||||||
sourceMap: true,
|
sourceMap: true,
|
||||||
onError: e => errors.push(e)
|
onError: e => errors.push(e)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// inMap should be the map produced by ./parse.ts which is a simple line-only
|
||||||
|
// mapping. If it is present, we need to adjust the final map and errors to
|
||||||
|
// reflect the original line numbers.
|
||||||
|
if (inMap) {
|
||||||
|
if (map) {
|
||||||
|
map = mapLines(inMap, map)
|
||||||
|
}
|
||||||
|
if (errors.length) {
|
||||||
|
patchErrors(errors, source, inMap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return { code, source, errors, tips: [], map }
|
return { code, source, errors, tips: [], map }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function mapLines(oldMap: RawSourceMap, newMap: RawSourceMap): RawSourceMap {
|
||||||
|
if (!oldMap) return newMap
|
||||||
|
if (!newMap) return oldMap
|
||||||
|
|
||||||
|
const oldMapConsumer = new SourceMapConsumer(oldMap)
|
||||||
|
const newMapConsumer = new SourceMapConsumer(newMap)
|
||||||
|
const mergedMapGenerator = new SourceMapGenerator()
|
||||||
|
|
||||||
|
newMapConsumer.eachMapping(m => {
|
||||||
|
if (m.originalLine == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const origPosInOldMap = oldMapConsumer.originalPositionFor({
|
||||||
|
line: m.originalLine,
|
||||||
|
column: m.originalColumn
|
||||||
|
})
|
||||||
|
|
||||||
|
if (origPosInOldMap.source == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mergedMapGenerator.addMapping({
|
||||||
|
generated: {
|
||||||
|
line: m.generatedLine,
|
||||||
|
column: m.generatedColumn
|
||||||
|
},
|
||||||
|
original: {
|
||||||
|
line: origPosInOldMap.line, // map line
|
||||||
|
// use current column, since the oldMap produced by @vue/compiler-sfc
|
||||||
|
// does not
|
||||||
|
column: m.originalColumn
|
||||||
|
},
|
||||||
|
source: origPosInOldMap.source,
|
||||||
|
name: origPosInOldMap.name
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// source-map's type definition is incomplete
|
||||||
|
const generator = mergedMapGenerator as any
|
||||||
|
;(oldMapConsumer as any).sources.forEach((sourceFile: string) => {
|
||||||
|
generator._sources.add(sourceFile)
|
||||||
|
const sourceContent = oldMapConsumer.sourceContentFor(sourceFile)
|
||||||
|
if (sourceContent != null) {
|
||||||
|
mergedMapGenerator.setSourceContent(sourceFile, sourceContent)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
generator._sourceRoot = oldMap.sourceRoot
|
||||||
|
generator._file = oldMap.file
|
||||||
|
return generator.toJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
function patchErrors(
|
||||||
|
errors: CompilerError[],
|
||||||
|
source: string,
|
||||||
|
inMap: RawSourceMap
|
||||||
|
) {
|
||||||
|
const originalSource = inMap.sourcesContent![0]
|
||||||
|
const offset = originalSource.indexOf(source)
|
||||||
|
const lineOffset = originalSource.slice(0, offset).split(/\r?\n/).length - 1
|
||||||
|
errors.forEach(err => {
|
||||||
|
if (err.loc) {
|
||||||
|
err.loc.start.line += lineOffset
|
||||||
|
err.loc.start.offset += offset
|
||||||
|
if (err.loc.end !== err.loc.start) {
|
||||||
|
err.loc.end.line += lineOffset
|
||||||
|
err.loc.end.offset += offset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -14,8 +14,12 @@ export {
|
|||||||
} from './parse'
|
} from './parse'
|
||||||
export {
|
export {
|
||||||
TemplateCompiler,
|
TemplateCompiler,
|
||||||
TemplateCompileOptions,
|
SFCTemplateCompileOptions,
|
||||||
TemplateCompileResults
|
SFCTemplateCompileResults
|
||||||
} from './compileTemplate'
|
} from './compileTemplate'
|
||||||
export { StyleCompileOptions, StyleCompileResults } from './compileStyle'
|
export { SFCStyleCompileOptions, SFCStyleCompileResults } from './compileStyle'
|
||||||
export { CompilerOptions, generateCodeFrame } from '@vue/compiler-core'
|
export {
|
||||||
|
CompilerOptions,
|
||||||
|
CompilerError,
|
||||||
|
generateCodeFrame
|
||||||
|
} from '@vue/compiler-core'
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user