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:
pikax 2020-04-08 21:21:04 +01:00
commit 5ae74144f2
339 changed files with 26645 additions and 8965 deletions

View File

@ -1,37 +1,63 @@
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:
build:
docker:
- image: vuejs/ci
working_directory: ~/repo
test:
<<: *defaults
steps:
- checkout
- *restore_cache
- *install_deps
- *save_cache
- run: yarn ls-lint
- run: yarn test --ci --runInBand
- restore_cache:
keys:
- v1-dependencies-{{ checksum "yarn.lock" }}
- v1-dependencies-
test-dts:
<<: *defaults
steps:
- checkout
- *restore_cache
- *install_deps
- *save_cache
- run: yarn test-dts
- run:
name: Install Dependencies
command: yarn --frozen-lockfile
check-size:
<<: *defaults
steps:
- checkout
- *restore_cache
- *install_deps
- *save_cache
- run: yarn size
- save_cache:
paths:
- node_modules
- ~/.cache/yarn
key: v1-dependencies-{{ checksum "yarn.lock" }}
- run:
name: Run Tests
command: yarn test --ci --runInBand
- run:
name: Test d.ts
command: yarn test-dts
- run:
name: Check size
command: yarn size
workflows:
version: 2
ci:
jobs:
- test
- test-dts
- check-size

2
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,2 @@
open_collective: vuejs
patreon: evanyou

View File

@ -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.
- If adding a new feature:
- Add accompanying test case.
- Provide a convincing reason to add this feature. Ideally, you should open a suggestion issue first and have it approved before working on it.
- If fixing bug:
- If you are resolving a special issue, add `(fix #xxxx[,#xxxx])` (#xxxx is the issue id) in your PR title for a better release log, e.g. `update entities encoding/decoding (fix #3899)`.
- Provide a detailed description of the bug in the PR. Live demo preferred.
- Add appropriate test coverage if applicable. You can check the coverage of your code addition by running `yarn test --coverage`.
@ -41,7 +43,7 @@ You will need [Node.js](http://nodejs.org) **version 10+**, and [Yarn](https://y
After cloning the repo, run:
``` bash
```bash
$ 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:
``` bash
```bash
# build runtime-core only
yarn build runtime-core
@ -68,38 +70,64 @@ yarn build runtime-core
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:
- **`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`.
- **`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)
- **`cjs`**: for use in Node.js via `require()`.
- **`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`.
- 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:
``` bash
```bash
yarn build runtime-core -f global
```
Multiple formats can be specified as a comma-separated list:
``` bash
```bash
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
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 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`
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
> 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 also supports the `-s` flag for generating source maps, but it will make rebuilds slower.
### `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:
``` bash
```bash
# run all tests
$ 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-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.
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.
@ -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:
``` js
```js
import { h } from '@vue/runtime-core'
```
This is made possible via several configurations:
- 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/).
### Package Dependencies
```
+---------------------+
| |
| @vue/compiler-sfc |

16
.github/issue-template.md vendored Normal file
View 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
View 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
View 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).

View File

@ -1,32 +1,14 @@
# vue-next [![CircleCI](https://circleci.com/gh/vuejs/vue-next.svg?style=svg&circle-token=fb883a2d0a73df46e80b2e79fd430959d8f2b488)](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
- [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
There is a simple webpack-based setup with Single-File Component support available [here](https://github.com/vuejs/vue-next-webpack-preview).
- Runtime
- [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()`
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).
## Major TODOs:
- [ ] SFC compiler
- [ ] Server-side rendering
Also note that the current implementation requires native ES2015+ in the runtime environment and does not support IE11 (yet).
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.
## Contribution

View File

@ -5,8 +5,10 @@ module.exports = {
__TEST__: true,
__VERSION__: require('./package.json').version,
__BROWSER__: false,
__BUNDLER__: false,
__BUNDLER__: true,
__RUNTIME_COMPILE__: true,
__GLOBAL__: false,
__NODE_JS__: true,
__FEATURE_OPTIONS__: true,
__FEATURE_SUSPENSE__: true
},
@ -16,13 +18,20 @@ module.exports = {
'packages/*/src/**/*.ts',
'!packages/runtime-test/src/utils/**',
'!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'],
moduleNameMapper: {
'^@vue/(.*?)$': '<rootDir>/packages/$1/src'
'^@vue/(.*?)$': '<rootDir>/packages/$1/src',
vue: '<rootDir>/packages/vue/src'
},
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/']
}

View File

@ -1,26 +1,29 @@
{
"private": true,
"version": "3.0.0-alpha.0",
"version": "3.0.0-alpha.11",
"workspaces": [
"packages/*"
],
"scripts": {
"dev": "node scripts/dev.js",
"build": "node scripts/build.js",
"size-runtime": "node scripts/build.js 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",
"size": "node scripts/build.js vue runtime-dom size-check -p -f global",
"lint": "prettier --write --parser typescript \"packages/**/*.ts?(x)\"",
"ls-lint": "ls-lint",
"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",
"release": "node scripts/release.js"
"test-dts": "node scripts/build.js shared reactivity runtime-core runtime-dom -dt -f esm-bundler && tsd",
"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",
"tsd": {
"directory": "test-dts"
},
"gitHooks": {
"pre-commit": "lint-staged",
"pre-commit": "ls-lint && lint-staged",
"commit-msg": "node scripts/verifyCommit.js"
},
"lint-staged": {
@ -33,29 +36,39 @@
"git add"
]
},
"engines": {
"node": ">=10.0.0"
},
"devDependencies": {
"@microsoft/api-extractor": "^7.3.9",
"@rollup/plugin-commonjs": "^11.0.2",
"@rollup/plugin-json": "^4.0.0",
"@rollup/plugin-node-resolve": "^7.1.1",
"@rollup/plugin-replace": "^2.2.1",
"@types/jest": "^24.0.21",
"@types/jest": "^25.1.4",
"@types/puppeteer": "^2.0.0",
"brotli": "^1.3.2",
"chalk": "^2.4.2",
"conventional-changelog-cli": "^2.0.31",
"csstype": "^2.6.8",
"enquirer": "^2.3.2",
"execa": "^2.0.4",
"fs-extra": "^8.1.0",
"jest": "^24.9.0",
"jest": "^25.2.3",
"lint-staged": "^9.2.3",
"minimist": "^1.2.0",
"npm-run-all": "^4.1.5",
"prettier": "~1.14.0",
"puppeteer": "^2.0.0",
"rollup": "^1.19.4",
"rollup-plugin-terser": "^5.1.1",
"rollup-plugin-typescript2": "^0.24.0",
"rollup": "^2.2.0",
"rollup-plugin-terser": "^5.3.0",
"rollup-plugin-typescript2": "^0.27.0",
"semver": "^6.3.0",
"ts-jest": "^24.0.2",
"serve": "^11.3.0",
"ts-jest": "^25.2.1",
"tsd": "^0.11.0",
"typescript": "^3.7.0",
"yorkie": "^2.0.0"
"typescript": "^3.8.3",
"yorkie": "^2.0.0",
"@ls-lint/ls-lint": "^1.8.0"
}
}

View File

@ -2,8 +2,8 @@
exports[`compiler: codegen ArrayExpression 1`] = `
"
return function render() {
with (this) {
return function render(_ctx, _cache) {
with (_ctx) {
return [
foo,
bar(baz)
@ -14,22 +14,18 @@ return function render() {
exports[`compiler: codegen CacheExpression 1`] = `
"
export default function render() {
const _ctx = this
const _cache = _ctx.$cache
export function render(_ctx, _cache) {
return _cache[1] || (_cache[1] = foo)
}"
`;
exports[`compiler: codegen CacheExpression w/ isVNode: true 1`] = `
"
export default function render() {
const _ctx = this
const _cache = _ctx.$cache
export function render(_ctx, _cache) {
return _cache[1] || (
setBlockTracking(-1),
_setBlockTracking(-1),
_cache[1] = foo,
setBlockTracking(1),
_setBlockTracking(1),
_cache[1]
)
}"
@ -37,8 +33,8 @@ export default function render() {
exports[`compiler: codegen ConditionalExpression 1`] = `
"
return function render() {
with (this) {
return function render(_ctx, _cache) {
with (_ctx) {
return ok
? foo()
: orNot
@ -50,8 +46,8 @@ return function render() {
exports[`compiler: codegen Element (callExpression + objectExpression + TemplateChildNode[]) 1`] = `
"
return function render() {
with (this) {
return function render(_ctx, _cache) {
with (_ctx) {
return _createVNode(\\"div\\", {
id: \\"foo\\",
[prop]: bar,
@ -63,24 +59,17 @@ return function render() {
}"
`;
exports[`compiler: codegen SequenceExpression 1`] = `
exports[`compiler: codegen assets + temps 1`] = `
"
return function render() {
with (this) {
return (foo, bar(baz))
}
}"
`;
exports[`compiler: codegen assets 1`] = `
"
return function render() {
with (this) {
return function render(_ctx, _cache) {
with (_ctx) {
const _component_Foo = _resolveComponent(\\"Foo\\")
const _component_bar_baz = _resolveComponent(\\"bar-baz\\")
const _component_barbaz = _resolveComponent(\\"barbaz\\")
const _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
}
}"
@ -88,8 +77,8 @@ return function render() {
exports[`compiler: codegen comment 1`] = `
"
return function render() {
with (this) {
return function render(_ctx, _cache) {
with (_ctx) {
return _createCommentVNode(\\"foo\\")
}
}"
@ -97,18 +86,18 @@ return function render() {
exports[`compiler: codegen compound expression 1`] = `
"
return function render() {
with (this) {
return _ctx.foo + _toString(bar)
return function render(_ctx, _cache) {
with (_ctx) {
return _ctx.foo + _toDisplayString(bar) + nested
}
}"
`;
exports[`compiler: codegen forNode 1`] = `
"
return function render() {
with (this) {
return (foo, bar)
return function render(_ctx, _cache) {
with (_ctx) {
return (_openBlock(true), _createBlock(_Fragment, null, _renderList(), 1))
}
}"
`;
@ -116,20 +105,19 @@ return function render() {
exports[`compiler: codegen function mode preamble 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
return function render(_ctx, _cache) {
with (_ctx) {
const { createVNode: _createVNode, resolveDirective: _resolveDirective } = _Vue
return null
}
}"
`;
exports[`compiler: codegen function mode preamble w/ prefixIdentifiers: true 1`] = `
"const { createVNode, resolveDirective } = Vue
"const { createVNode: _createVNode, resolveDirective: _resolveDirective } = Vue
return function render() {
const _ctx = this
return function render(_ctx, _cache) {
return null
}"
`;
@ -139,8 +127,8 @@ exports[`compiler: codegen hoists 1`] = `
const _hoisted_1 = hello
const _hoisted_2 = { id: \\"foo\\" }
return function render() {
with (this) {
return function render(_ctx, _cache) {
with (_ctx) {
return null
}
}"
@ -148,44 +136,59 @@ return function render() {
exports[`compiler: codegen ifNode 1`] = `
"
return function render() {
with (this) {
return (foo, bar)
return function render(_ctx, _cache) {
with (_ctx) {
return foo
? bar
: baz
}
}"
`;
exports[`compiler: codegen interpolation 1`] = `
"
return function render() {
with (this) {
return _toString(hello)
return function render(_ctx, _cache) {
with (_ctx) {
return _toDisplayString(hello)
}
}"
`;
exports[`compiler: codegen module mode preamble 1`] = `
"import { createVNode, resolveDirective } from \\"vue\\"
"import { createVNode as _createVNode, resolveDirective as _resolveDirective } from \\"vue\\"
export default function render() {
const _ctx = this
export function render(_ctx, _cache) {
return null
}"
`;
exports[`compiler: codegen prefixIdentifiers: true should inject _ctx statement 1`] = `
"
return function render() {
const _ctx = this
exports[`compiler: codegen module mode preamble w/ optimizeBindings: true 1`] = `
"import { createVNode, resolveDirective } from \\"vue\\"
// Binding optimization for webpack code-split
const _createVNode = createVNode, _resolveDirective = resolveDirective
export function render(_ctx, _cache) {
return null
}"
`;
exports[`compiler: codegen static text 1`] = `
"
return function render() {
with (this) {
return function render(_ctx, _cache) {
with (_ctx) {
return \\"hello\\"
}
}"
`;
exports[`compiler: codegen temps 1`] = `
"
return function render(_ctx, _cache) {
with (_ctx) {
let _temp0, _temp1, _temp2
return null
}
}"
`;

View File

@ -3,21 +3,23 @@
exports[`compiler: integration tests function mode 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { toString: _toString, openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, createCommentVNode: _createCommentVNode, Fragment: _Fragment, renderList: _renderList, createTextVNode: _createTextVNode } = _Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { toDisplayString: _toDisplayString, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock, createCommentVNode: _createCommentVNode, createTextVNode: _createTextVNode, Fragment: _Fragment, renderList: _renderList } = _Vue
return (_openBlock(), _createBlock(\\"div\\", {
id: \\"foo\\",
class: bar.baz
}, [
_createTextVNode(_toString(world.burn()) + \\" \\", 1 /* TEXT */),
(_openBlock(), ok
? _createBlock(\\"div\\", { key: 0 }, \\"yes\\")
: _createBlock(_Fragment, { key: 1 }, [\\"no\\"])),
(_openBlock(false), _createBlock(_Fragment, null, _renderList(list, (value, index) => {
_createTextVNode(_toDisplayString(world.burn()) + \\" \\", 1 /* TEXT */),
ok
? (_openBlock(), _createBlock(\\"div\\", { key: 0 }, \\"yes\\"))
: (_openBlock(), _createBlock(_Fragment, { key: 1 }, [
_createTextVNode(\\"no\\")
], 64 /* STABLE_FRAGMENT */)),
(_openBlock(true), _createBlock(_Fragment, null, _renderList(list, (value, index) => {
return (_openBlock(), _createBlock(\\"div\\", null, [
_createVNode(\\"span\\", null, _toString(value + index), 1 /* TEXT */)
_createVNode(\\"span\\", null, _toDisplayString(value + index), 1 /* TEXT */)
]))
}), 256 /* UNKEYED_FRAGMENT */))
], 2 /* CLASS */))
@ -26,21 +28,22 @@ return function render() {
`;
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() {
const _ctx = this
return (openBlock(), createBlock(\\"div\\", {
return function render(_ctx, _cache) {
return (_openBlock(), _createBlock(\\"div\\", {
id: \\"foo\\",
class: _ctx.bar.baz
}, [
createTextVNode(toString(_ctx.world.burn()) + \\" \\", 1 /* TEXT */),
(openBlock(), (_ctx.ok)
? createBlock(\\"div\\", { key: 0 }, \\"yes\\")
: createBlock(Fragment, { key: 1 }, [\\"no\\"])),
(openBlock(false), createBlock(Fragment, null, renderList(_ctx.list, (value, index) => {
return (openBlock(), createBlock(\\"div\\", null, [
createVNode(\\"span\\", null, toString(value + index), 1 /* TEXT */)
_createTextVNode(_toDisplayString(_ctx.world.burn()) + \\" \\", 1 /* TEXT */),
(_ctx.ok)
? (_openBlock(), _createBlock(\\"div\\", { key: 0 }, \\"yes\\"))
: (_openBlock(), _createBlock(_Fragment, { key: 1 }, [
_createTextVNode(\\"no\\")
], 64 /* STABLE_FRAGMENT */)),
(_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 */))
], 2 /* CLASS */))
@ -48,21 +51,22 @@ return function render() {
`;
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() {
const _ctx = this
return (openBlock(), createBlock(\\"div\\", {
export function render(_ctx, _cache) {
return (_openBlock(), _createBlock(\\"div\\", {
id: \\"foo\\",
class: _ctx.bar.baz
}, [
createTextVNode(toString(_ctx.world.burn()) + \\" \\", 1 /* TEXT */),
(openBlock(), (_ctx.ok)
? createBlock(\\"div\\", { key: 0 }, \\"yes\\")
: createBlock(Fragment, { key: 1 }, [\\"no\\"])),
(openBlock(false), createBlock(Fragment, null, renderList(_ctx.list, (value, index) => {
return (openBlock(), createBlock(\\"div\\", null, [
createVNode(\\"span\\", null, toString(value + index), 1 /* TEXT */)
_createTextVNode(_toDisplayString(_ctx.world.burn()) + \\" \\", 1 /* TEXT */),
(_ctx.ok)
? (_openBlock(), _createBlock(\\"div\\", { key: 0 }, \\"yes\\"))
: (_openBlock(), _createBlock(_Fragment, { key: 1 }, [
_createTextVNode(\\"no\\")
], 64 /* STABLE_FRAGMENT */)),
(_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 */))
], 2 /* CLASS */))

View File

@ -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\\"))
})"
`;

View File

@ -9,20 +9,28 @@ import {
createArrayExpression,
createCompoundExpression,
createInterpolation,
createSequenceExpression,
createCallExpression,
createConditionalExpression,
IfCodegenNode,
ForCodegenNode,
createCacheExpression
createCacheExpression,
createTemplateLiteral,
createBlockStatement,
createIfStatement,
createAssignmentExpression,
IfConditionalExpression,
createVNodeCall,
VNodeCall,
DirectiveArguments
} from '../src'
import {
CREATE_VNODE,
TO_STRING,
TO_DISPLAY_STRING,
RESOLVE_DIRECTIVE,
helperNameMap,
RESOLVE_COMPONENT,
CREATE_COMMENT
CREATE_COMMENT,
FRAGMENT,
RENDER_LIST
} from '../src/runtimeHelpers'
import { createElementWithCodegen } from './testUtils'
import { PatchFlags } from '@vue/shared'
@ -37,6 +45,7 @@ function createRoot(options: Partial<RootNode> = {}): RootNode {
imports: [],
hoists: [],
cached: 0,
temps: 0,
codegenNode: createSimpleExpression(`null`, false),
loc: locStub,
...options
@ -49,11 +58,33 @@ describe('compiler: codegen', () => {
helpers: [CREATE_VNODE, RESOLVE_DIRECTIVE]
})
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(
`import { ${helperNameMap[CREATE_VNODE]}, ${
helperNameMap[RESOLVE_DIRECTIVE]
} } from "vue"`
)
expect(code).toMatch(
`const _${helperNameMap[CREATE_VNODE]} = ${
helperNameMap[CREATE_VNODE]
}, _${helperNameMap[RESOLVE_DIRECTIVE]} = ${
helperNameMap[RESOLVE_DIRECTIVE]
}`
)
expect(code).toMatchSnapshot()
})
@ -83,17 +114,20 @@ describe('compiler: codegen', () => {
})
expect(code).not.toMatch(`const _Vue = Vue`)
expect(code).toMatch(
`const { ${helperNameMap[CREATE_VNODE]}, ${
`const { ${helperNameMap[CREATE_VNODE]}: _${
helperNameMap[CREATE_VNODE]
}, ${helperNameMap[RESOLVE_DIRECTIVE]}: _${
helperNameMap[RESOLVE_DIRECTIVE]
} } = Vue`
)
expect(code).toMatchSnapshot()
})
test('assets', () => {
test('assets + temps', () => {
const root = createRoot({
components: [`Foo`, `bar-baz`, `barbaz`],
directives: [`my_dir`]
directives: [`my_dir_0`, `my_dir_1`],
temps: 3
})
const { code } = generate(root, { mode: 'function' })
expect(code).toMatch(
@ -110,10 +144,16 @@ describe('compiler: codegen', () => {
}("barbaz")\n`
)
expect(code).toMatch(
`const _directive_my_dir = _${
`const _directive_my_dir_0 = _${
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()
})
@ -138,9 +178,12 @@ describe('compiler: codegen', () => {
expect(code).toMatchSnapshot()
})
test('prefixIdentifiers: true should inject _ctx statement', () => {
const { code } = generate(createRoot(), { prefixIdentifiers: true })
expect(code).toMatch(`const _ctx = this\n`)
test('temps', () => {
const root = createRoot({
temps: 3
})
const { code } = generate(root)
expect(code).toMatch(`let _temp0, _temp1, _temp2`)
expect(code).toMatchSnapshot()
})
@ -164,7 +207,7 @@ describe('compiler: codegen', () => {
codegenNode: createInterpolation(`hello`, locStub)
})
)
expect(code).toMatch(`return _${helperNameMap[TO_STRING]}(hello)`)
expect(code).toMatch(`return _${helperNameMap[TO_DISPLAY_STRING]}(hello)`)
expect(code).toMatchSnapshot()
})
@ -193,11 +236,15 @@ describe('compiler: codegen', () => {
type: NodeTypes.INTERPOLATION,
loc: 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()
})
@ -208,14 +255,15 @@ describe('compiler: codegen', () => {
type: NodeTypes.IF,
loc: locStub,
branches: [],
codegenNode: createSequenceExpression([
codegenNode: createConditionalExpression(
createSimpleExpression('foo', false),
createSimpleExpression('bar', false)
]) as IfCodegenNode
createSimpleExpression('bar', false),
createSimpleExpression('baz', false)
) as IfConditionalExpression
}
})
)
expect(code).toMatch(`return (foo, bar)`)
expect(code).toMatch(/return foo\s+\? bar\s+: baz/)
expect(code).toMatchSnapshot()
})
@ -230,21 +278,30 @@ describe('compiler: codegen', () => {
keyAlias: undefined,
objectIndexAlias: undefined,
children: [],
codegenNode: createSequenceExpression([
createSimpleExpression('foo', false),
createSimpleExpression('bar', false)
]) as ForCodegenNode
parseResult: {} as any,
codegenNode: {
type: NodeTypes.VNODE_CALL,
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()
})
test('Element (callExpression + objectExpression + TemplateChildNode[])', () => {
const { code } = generate(
createRoot({
codegenNode: createElementWithCodegen([
codegenNode: createElementWithCodegen(
// string
`"div"`,
// ObjectExpression
@ -275,7 +332,7 @@ describe('compiler: codegen', () => {
),
// ChildNode[]
[
createElementWithCodegen([
createElementWithCodegen(
`"p"`,
createObjectExpression(
[
@ -287,11 +344,11 @@ describe('compiler: codegen', () => {
],
locStub
)
])
)
],
// flag
PatchFlags.FULL_PROPS + ''
])
)
})
)
expect(code).toMatch(`
@ -321,19 +378,6 @@ describe('compiler: codegen', () => {
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', () => {
const { code } = generate(
createRoot({
@ -372,7 +416,6 @@ describe('compiler: codegen', () => {
prefixIdentifiers: true
}
)
expect(code).toMatch(`const _cache = _ctx.$cache`)
expect(code).toMatch(`_cache[1] || (_cache[1] = foo)`)
expect(code).toMatchSnapshot()
})
@ -392,17 +435,309 @@ describe('compiler: codegen', () => {
prefixIdentifiers: true
}
)
expect(code).toMatch(`const _cache = _ctx.$cache`)
expect(code).toMatch(
`
_cache[1] || (
setBlockTracking(-1),
_setBlockTracking(-1),
_cache[1] = foo,
setBlockTracking(1),
_setBlockTracking(1),
_cache[1]
)
`.trim()
)
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]
])
"
`)
})
})
})

View File

@ -44,7 +44,7 @@ describe('compiler: integration tests', () => {
return res
}
test('function mode', async () => {
test('function mode', () => {
const { code, map } = compile(source, {
sourceMap: true,
filename: `foo.vue`
@ -54,7 +54,7 @@ describe('compiler: integration tests', () => {
expect(map!.sources).toEqual([`foo.vue`])
expect(map!.sourcesContent).toEqual([source])
const consumer = await new SourceMapConsumer(map as RawSourceMap)
const consumer = new SourceMapConsumer(map as RawSourceMap)
expect(
consumer.originalPositionFor(getPositionInCode(code, `id`))
@ -109,7 +109,7 @@ describe('compiler: integration tests', () => {
).toMatchObject(getPositionInCode(source, `value + index`))
})
test('function mode w/ prefixIdentifiers: true', async () => {
test('function mode w/ prefixIdentifiers: true', () => {
const { code, map } = compile(source, {
sourceMap: true,
filename: `foo.vue`,
@ -120,7 +120,7 @@ describe('compiler: integration tests', () => {
expect(map!.sources).toEqual([`foo.vue`])
expect(map!.sourcesContent).toEqual([source])
const consumer = await new SourceMapConsumer(map as RawSourceMap)
const consumer = new SourceMapConsumer(map as RawSourceMap)
expect(
consumer.originalPositionFor(getPositionInCode(code, `id`))
@ -184,7 +184,7 @@ describe('compiler: integration tests', () => {
).toMatchObject(getPositionInCode(source, `value + index`))
})
test('module mode', async () => {
test('module mode', () => {
const { code, map } = compile(source, {
mode: 'module',
sourceMap: true,
@ -195,7 +195,7 @@ describe('compiler: integration tests', () => {
expect(map!.sources).toEqual([`foo.vue`])
expect(map!.sourcesContent).toEqual([source])
const consumer = await new SourceMapConsumer(map as RawSourceMap)
const consumer = new SourceMapConsumer(map as RawSourceMap)
expect(
consumer.originalPositionFor(getPositionInCode(code, `id`))

View File

@ -1,5 +1,5 @@
import { ParserOptions } from '../src/options'
import { parse, TextModes } from '../src/parse'
import { baseParse, TextModes } from '../src/parse'
import { ErrorCodes } from '../src/errors'
import {
CommentNode,
@ -16,7 +16,7 @@ import {
describe('compiler: parse', () => {
describe('Text', () => {
test('simple text', () => {
const ast = parse('some text')
const ast = baseParse('some text')
const text = ast.children[0] as TextNode
expect(text).toStrictEqual({
@ -31,7 +31,7 @@ describe('compiler: parse', () => {
})
test('simple text with invalid end tag', () => {
const ast = parse('some text</div>', {
const ast = baseParse('some text</div>', {
onError: () => {}
})
const text = ast.children[0] as TextNode
@ -48,7 +48,7 @@ describe('compiler: parse', () => {
})
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 text2 = ast.children[2] as TextNode
@ -73,7 +73,7 @@ describe('compiler: parse', () => {
})
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 text2 = ast.children[2] as TextNode
@ -98,7 +98,7 @@ describe('compiler: parse', () => {
})
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 text2 = (ast.children[1] as ElementNode).children![1] as TextNode
@ -123,7 +123,7 @@ describe('compiler: parse', () => {
})
test('lonly "<" don\'t separate nodes', () => {
const ast = parse('a < b', {
const ast = baseParse('a < b', {
onError: err => {
if (err.code !== ErrorCodes.INVALID_FIRST_CHARACTER_OF_TAG_NAME) {
throw err
@ -144,7 +144,7 @@ describe('compiler: parse', () => {
})
test('lonly "{{" don\'t separate nodes', () => {
const ast = parse('a {{ b', {
const ast = baseParse('a {{ b', {
onError: error => {
if (error.code !== ErrorCodes.X_MISSING_INTERPOLATION_END) {
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).', () => {
const spy = jest.fn()
const ast = parse('&ampersand;', {
const ast = baseParse('&ampersand;', {
namedCharacterReferences: { amp: '&' },
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).', () => {
const spy = jest.fn()
const ast = parse(
const ast = baseParse(
'<div a="&ampersand;" b="&amp;ersand;" c="&amp!"></div>',
{
namedCharacterReferences: { amp: '&', 'amp;': '&' },
@ -248,7 +248,7 @@ describe('compiler: parse', () => {
test('Some control character reference should be replaced.', () => {
const spy = jest.fn()
const ast = parse('&#x86;', { onError: spy })
const ast = baseParse('&#x86;', { onError: spy })
const text = ast.children[0] as TextNode
expect(text).toStrictEqual({
@ -275,7 +275,7 @@ describe('compiler: parse', () => {
describe('Interpolation', () => {
test('simple interpolation', () => {
const ast = parse('{{message}}')
const ast = baseParse('{{message}}')
const interpolation = ast.children[0] as InterpolationNode
expect(interpolation).toStrictEqual({
@ -300,7 +300,7 @@ describe('compiler: parse', () => {
})
test('it can have tag-like notation', () => {
const ast = parse('{{ a<b }}')
const ast = baseParse('{{ a<b }}')
const interpolation = ast.children[0] as InterpolationNode
expect(interpolation).toStrictEqual({
@ -325,7 +325,7 @@ describe('compiler: parse', () => {
})
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 interpolation2 = ast.children[1] as InterpolationNode
@ -371,7 +371,7 @@ describe('compiler: parse', () => {
})
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 interpolation = element.children[0] as InterpolationNode
@ -398,7 +398,7 @@ describe('compiler: parse', () => {
})
test('custom delimiters', () => {
const ast = parse('<p>{msg}</p>', {
const ast = baseParse('<p>{msg}</p>', {
delimiters: ['{', '}']
})
const element = ast.children[0] as ElementNode
@ -428,7 +428,7 @@ describe('compiler: parse', () => {
describe('Comment', () => {
test('empty comment', () => {
const ast = parse('<!---->')
const ast = baseParse('<!---->')
const comment = ast.children[0] as CommentNode
expect(comment).toStrictEqual({
@ -443,7 +443,7 @@ describe('compiler: parse', () => {
})
test('simple comment', () => {
const ast = parse('<!--abc-->')
const ast = baseParse('<!--abc-->')
const comment = ast.children[0] as CommentNode
expect(comment).toStrictEqual({
@ -458,7 +458,7 @@ describe('compiler: parse', () => {
})
test('two comments', () => {
const ast = parse('<!--abc--><!--def-->')
const ast = baseParse('<!--abc--><!--def-->')
const comment1 = ast.children[0] as CommentNode
const comment2 = ast.children[1] as CommentNode
@ -485,7 +485,7 @@ describe('compiler: parse', () => {
describe('Element', () => {
test('simple div', () => {
const ast = parse('<div>hello</div>')
const ast = baseParse('<div>hello</div>')
const element = ast.children[0] as ElementNode
expect(element).toStrictEqual({
@ -516,7 +516,7 @@ describe('compiler: parse', () => {
})
test('empty', () => {
const ast = parse('<div></div>')
const ast = baseParse('<div></div>')
const element = ast.children[0] as ElementNode
expect(element).toStrictEqual({
@ -526,7 +526,6 @@ describe('compiler: parse', () => {
tagType: ElementTypes.ELEMENT,
codegenNode: undefined,
props: [],
isSelfClosing: false,
children: [],
loc: {
@ -538,7 +537,7 @@ describe('compiler: parse', () => {
})
test('self closing', () => {
const ast = parse('<div/>after')
const ast = baseParse('<div/>after')
const element = ast.children[0] as ElementNode
expect(element).toStrictEqual({
@ -560,7 +559,7 @@ describe('compiler: parse', () => {
})
test('void element', () => {
const ast = parse('<img>after', {
const ast = baseParse('<img>after', {
isVoidTag: tag => tag === 'img'
})
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`', () => {
const ast = parse('<div></div><comp></comp><Comp></Comp>', {
const ast = baseParse('<div></div><comp></comp><Comp></Comp>', {
isNativeTag: tag => tag === 'div'
})
@ -608,7 +625,7 @@ describe('compiler: parse', () => {
})
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({
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', () => {
const ast = parse('<div></div><comp></comp>', {
const ast = baseParse('<div></div><comp></comp>', {
isNativeTag: tag => tag === 'div',
isCustomElement: tag => tag === 'comp'
})
@ -649,7 +715,7 @@ describe('compiler: parse', () => {
})
test('attribute with no value', () => {
const ast = parse('<div id></div>')
const ast = baseParse('<div id></div>')
const element = ast.children[0] as ElementNode
expect(element).toStrictEqual({
@ -682,7 +748,7 @@ describe('compiler: parse', () => {
})
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
expect(element).toStrictEqual({
@ -723,7 +789,7 @@ describe('compiler: parse', () => {
})
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
expect(element).toStrictEqual({
@ -764,7 +830,7 @@ describe('compiler: parse', () => {
})
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
expect(element).toStrictEqual({
@ -805,7 +871,7 @@ describe('compiler: parse', () => {
})
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
expect(element).toStrictEqual({
@ -846,7 +912,7 @@ describe('compiler: parse', () => {
})
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
expect(element).toStrictEqual({
@ -887,7 +953,7 @@ describe('compiler: parse', () => {
})
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
expect(element).toStrictEqual({
@ -974,7 +1040,7 @@ describe('compiler: parse', () => {
})
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]
expect(directive).toStrictEqual({
@ -992,7 +1058,7 @@ describe('compiler: parse', () => {
})
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]
expect(directive).toStrictEqual({
@ -1020,7 +1086,7 @@ describe('compiler: parse', () => {
})
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]
expect(directive).toStrictEqual({
@ -1057,7 +1123,7 @@ describe('compiler: parse', () => {
})
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]
expect(directive).toStrictEqual({
@ -1075,7 +1141,7 @@ describe('compiler: parse', () => {
})
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]
expect(directive).toStrictEqual({
@ -1093,7 +1159,7 @@ describe('compiler: parse', () => {
})
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]
expect(directive).toStrictEqual({
@ -1130,7 +1196,7 @@ describe('compiler: parse', () => {
})
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]
expect(directive).toStrictEqual({
@ -1178,7 +1244,7 @@ describe('compiler: parse', () => {
})
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]
expect(directive).toStrictEqual({
@ -1226,7 +1292,7 @@ describe('compiler: parse', () => {
})
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]
expect(directive).toStrictEqual({
@ -1274,7 +1340,7 @@ describe('compiler: parse', () => {
})
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]
expect(directive).toStrictEqual({
@ -1322,7 +1388,7 @@ describe('compiler: parse', () => {
})
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]
expect(directive).toStrictEqual({
@ -1369,7 +1435,7 @@ describe('compiler: parse', () => {
})
test('v-pre', () => {
const ast = parse(
const ast = baseParse(
`<div v-pre :id="foo"><Comp/>{{ bar }}</div>\n` +
`<div :id="foo"><Comp/>{{ bar }}</div>`
)
@ -1451,7 +1517,7 @@ describe('compiler: parse', () => {
})
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 text = element.children[0] as TextNode
@ -1468,14 +1534,14 @@ describe('compiler: parse', () => {
})
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[0]).toMatchObject({ tag: 'div' })
})
test('self closing multiple tag', () => {
const ast = parse(
const ast = baseParse(
`<div :class="{ some: condition }" />\n` +
`<p v-bind:style="{ color: 'red' }"/>`
)
@ -1488,7 +1554,7 @@ describe('compiler: parse', () => {
})
test('valid html', () => {
const ast = parse(
const ast = baseParse(
`<div :class="{ some: condition }">\n` +
` <p v-bind:style="{ color: 'red' }"/>\n` +
` <!-- a comment with <html> inside it -->\n` +
@ -1513,11 +1579,11 @@ describe('compiler: parse', () => {
test('invalid html', () => {
expect(() => {
parse(`<div>\n<span>\n</div>\n</span>`)
}).toThrow('End tag was not found. (3:1)')
baseParse(`<div>\n<span>\n</div>\n</span>`)
}).toThrow('Element is missing end tag.')
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
})
@ -1527,8 +1593,8 @@ describe('compiler: parse', () => {
code: ErrorCodes.X_MISSING_END_TAG,
loc: {
start: {
offset: 13,
line: 3,
offset: 6,
line: 2,
column: 1
}
}
@ -1552,7 +1618,7 @@ describe('compiler: parse', () => {
})
test('parse with correct location info', () => {
const [foo, bar, but, baz] = parse(
const [foo, bar, but, baz] = baseParse(
`
foo
is {{ bar }} but {{ baz }}`.trim()
@ -1588,7 +1654,7 @@ foo
describe('namedCharacterReferences option', () => {
test('use the given map', () => {
const ast: any = parse('&amp;&cups;', {
const ast: any = baseParse('&amp;&cups;', {
namedCharacterReferences: {
'cups;': '\u222A\uFE00' // UNION with serifs
},
@ -1603,18 +1669,18 @@ foo
describe('whitespace management', () => {
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)
})
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.every(c => c.type === NodeTypes.ELEMENT)).toBe(true)
})
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[0].type).toBe(NodeTypes.ELEMENT)
expect(ast.children[1].type).toBe(NodeTypes.COMMENT)
@ -1622,7 +1688,7 @@ foo
})
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[0].type).toBe(NodeTypes.ELEMENT)
expect(ast.children[1].type).toBe(NodeTypes.COMMENT)
@ -1630,7 +1696,7 @@ foo
})
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[0].type).toBe(NodeTypes.INTERPOLATION)
expect(ast.children[1]).toMatchObject({
@ -1641,7 +1707,7 @@ foo
})
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.map(c => c.type)).toMatchObject([
NodeTypes.ELEMENT,
@ -1653,7 +1719,7 @@ foo
})
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 `)
})
})
@ -1833,7 +1899,7 @@ foo
},
{
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,
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,
loc: { offset: 29, line: 1, column: 30 }
loc: { offset: 10, line: 1, column: 11 }
},
{
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,
loc: { offset: 24, line: 1, column: 25 }
loc: { offset: 10, line: 1, column: 11 }
},
{
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,
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,
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,
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,
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,
loc: { offset: 15, line: 1, column: 16 }
loc: { offset: 0, line: 1, column: 1 }
}
]
}
@ -1962,7 +2028,7 @@ foo
errors: [
{
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,
@ -1975,7 +2041,7 @@ foo
errors: [
{
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,
loc: { offset: 14, line: 1, column: 15 }
loc: { offset: 10, line: 1, column: 11 }
},
{
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,
loc: { offset: 15, line: 1, column: 16 }
loc: { offset: 10, line: 1, column: 11 }
},
{
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,
loc: { offset: 17, line: 1, column: 18 }
loc: { offset: 10, line: 1, column: 11 }
},
{
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,
loc: { offset: 18, line: 1, column: 19 }
loc: { offset: 10, line: 1, column: 11 }
},
{
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,
loc: { offset: 19, line: 1, column: 20 }
loc: { offset: 10, line: 1, column: 11 }
},
{
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,
loc: { offset: 22, line: 1, column: 23 }
loc: { offset: 10, line: 1, column: 11 }
},
{
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,
loc: { offset: 22, line: 1, column: 23 }
loc: { offset: 10, line: 1, column: 11 }
},
{
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,
loc: { offset: 23, line: 1, column: 24 }
loc: { offset: 10, line: 1, column: 11 }
},
{
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,
loc: { offset: 23, line: 1, column: 24 }
loc: { offset: 10, line: 1, column: 11 }
},
{
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,
loc: { offset: 21, line: 1, column: 22 }
loc: { offset: 10, line: 1, column: 11 }
},
{
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,
loc: { offset: 24, line: 1, column: 25 }
loc: { offset: 10, line: 1, column: 11 }
},
{
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,
loc: { offset: 24, line: 1, column: 25 }
loc: { offset: 10, line: 1, column: 11 }
},
{
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,
loc: { offset: 23, line: 1, column: 24 }
loc: { offset: 10, line: 1, column: 11 }
},
{
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,
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,
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: [
{
code: '<template></div></template>',
@ -2651,7 +2706,7 @@ foo
errors: [
{
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: [
{
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,
loc: { offset: 15, line: 1, column: 16 }
loc: { offset: 0, line: 1, column: 1 }
}
]
}
@ -2716,7 +2771,7 @@ foo
),
() => {
const spy = jest.fn()
const ast = parse(code, {
const ast = baseParse(code, {
getNamespace: (tag, parent) => {
const ns = parent ? parent.ns : Namespaces.HTML
if (ns === Namespaces.HTML) {

View 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()
})
})

View File

@ -4,9 +4,8 @@ import {
locStub,
Namespaces,
ElementTypes,
PlainElementCodegenNode
VNodeCall
} from '../src'
import { CREATE_VNODE } from '../src/runtimeHelpers'
import { isString, PatchFlags, PatchFlagNames, isArray } from '@vue/shared'
const leadingBracketRE = /^\[/
@ -39,7 +38,11 @@ export function createObjectMatcher(obj: Record<string, any>) {
}
export function createElementWithCodegen(
args: PlainElementCodegenNode['arguments']
tag: VNodeCall['tag'],
props?: VNodeCall['props'],
children?: VNodeCall['children'],
patchFlag?: VNodeCall['patchFlag'],
dynamicProps?: VNodeCall['dynamicProps']
): ElementNode {
return {
type: NodeTypes.ELEMENT,
@ -51,10 +54,16 @@ export function createElementWithCodegen(
props: [],
children: [],
codegenNode: {
type: NodeTypes.JS_CALL_EXPRESSION,
loc: locStub,
callee: CREATE_VNODE,
arguments: args
type: NodeTypes.VNODE_CALL,
tag,
props,
children,
patchFlag,
dynamicProps,
directives: undefined,
isBlock: false,
isForBlock: false,
loc: locStub
}
}
}

View File

@ -1,19 +1,17 @@
import { parse } from '../src/parse'
import { baseParse } from '../src/parse'
import { transform, NodeTransform } from '../src/transform'
import {
ElementNode,
NodeTypes,
DirectiveNode,
ExpressionNode
ExpressionNode,
VNodeCall
} from '../src/ast'
import { ErrorCodes, createCompilerError } from '../src/errors'
import {
TO_STRING,
OPEN_BLOCK,
CREATE_BLOCK,
TO_DISPLAY_STRING,
FRAGMENT,
RENDER_SLOT,
WITH_DIRECTIVES,
CREATE_COMMENT
} from '../src/runtimeHelpers'
import { transformIf } from '../src/transforms/vIf'
@ -26,7 +24,7 @@ import { PatchFlags } from '@vue/shared'
describe('compiler: transform', () => {
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
// across calls
@ -72,7 +70,7 @@ describe('compiler: transform', () => {
})
test('context.replaceNode', () => {
const ast = parse(`<div/><span/>`)
const ast = baseParse(`<div/><span/>`)
const plugin: NodeTransform = (node, context) => {
if (node.type === NodeTypes.ELEMENT && node.tag === 'div') {
// change the node to <p>
@ -106,7 +104,7 @@ describe('compiler: transform', () => {
})
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 c2 = ast.children[2]
@ -132,7 +130,7 @@ describe('compiler: transform', () => {
})
test('context.removeNode (prev sibling)', () => {
const ast = parse(`<span/><div/><span/>`)
const ast = baseParse(`<span/><div/><span/>`)
const c1 = ast.children[0]
const c2 = ast.children[2]
@ -159,7 +157,7 @@ describe('compiler: transform', () => {
})
test('context.removeNode (next sibling)', () => {
const ast = parse(`<span/><div/><span/>`)
const ast = baseParse(`<span/><div/><span/>`)
const c1 = ast.children[0]
const d1 = ast.children[1]
@ -186,7 +184,7 @@ describe('compiler: transform', () => {
})
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 mock: NodeTransform = (node, context) => {
if (node.type === NodeTypes.ELEMENT) {
@ -204,7 +202,7 @@ describe('compiler: transform', () => {
})
test('onError option', () => {
const ast = parse(`<div/>`)
const ast = baseParse(`<div/>`)
const loc = ast.children[0].loc
const plugin: NodeTransform = (node, context) => {
context.onError(
@ -225,20 +223,20 @@ describe('compiler: transform', () => {
})
test('should inject toString helper for interpolations', () => {
const ast = parse(`{{ foo }}`)
const ast = baseParse(`{{ foo }}`)
transform(ast, {})
expect(ast.helpers).toContain(TO_STRING)
expect(ast.helpers).toContain(TO_DISPLAY_STRING)
})
test('should inject createVNode and Comment for comments', () => {
const ast = parse(`<!--foo-->`)
const ast = baseParse(`<!--foo-->`)
transform(ast, {})
expect(ast.helpers).toContain(CREATE_COMMENT)
})
describe('root codegenNode', () => {
function transformWithCodegen(template: string) {
const ast = parse(template)
const ast = baseParse(template)
transform(ast, {
nodeTransforms: [
transformIf,
@ -251,20 +249,19 @@ describe('compiler: transform', () => {
return ast
}
function createBlockMatcher(args: any[]) {
function createBlockMatcher(
tag: VNodeCall['tag'],
props?: VNodeCall['props'],
children?: VNodeCall['children'],
patchFlag?: VNodeCall['patchFlag']
) {
return {
type: NodeTypes.JS_SEQUENCE_EXPRESSION,
expressions: [
{
type: NodeTypes.JS_CALL_EXPRESSION,
callee: OPEN_BLOCK
},
{
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_BLOCK,
arguments: args
}
]
type: NodeTypes.VNODE_CALL,
isBlock: true,
tag,
props,
children,
patchFlag
}
}
@ -285,7 +282,7 @@ describe('compiler: transform', () => {
test('single element', () => {
const ast = transformWithCodegen(`<div/>`)
expect(ast.codegenNode).toMatchObject(createBlockMatcher([`"div"`]))
expect(ast.codegenNode).toMatchObject(createBlockMatcher(`"div"`))
})
test('root v-if', () => {
@ -305,22 +302,8 @@ describe('compiler: transform', () => {
test('root element with custom directive', () => {
const ast = transformWithCodegen(`<div v-foo/>`)
expect(ast.codegenNode).toMatchObject({
type: NodeTypes.JS_SEQUENCE_EXPRESSION,
expressions: [
{
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 }
]
}
]
type: NodeTypes.VNODE_CALL,
directives: { type: NodeTypes.JS_ARRAY_EXPRESSION }
})
})
@ -348,15 +331,15 @@ describe('compiler: transform', () => {
test('multiple children', () => {
const ast = transformWithCodegen(`<div/><div/>`)
expect(ast.codegenNode).toMatchObject(
createBlockMatcher([
createBlockMatcher(
FRAGMENT,
`null`,
undefined,
[
{ type: NodeTypes.ELEMENT, tag: `div` },
{ type: NodeTypes.ELEMENT, tag: `div` }
],
] as any,
genFlagText(PatchFlags.STABLE_FRAGMENT)
])
)
)
})
})

View File

@ -2,14 +2,14 @@
exports[`compiler: hoistStatic transform hoist element with static key 1`] = `
"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(_ctx, _cache) {
with (_ctx) {
const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return function render() {
with (this) {
const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return (_openBlock(), _createBlock(\\"div\\", null, [
_hoisted_1
]))
@ -19,17 +19,17 @@ return function render() {
exports[`compiler: hoistStatic transform hoist nested static tree 1`] = `
"const _Vue = Vue
const { createVNode: _createVNode } = Vue
const { createVNode: _createVNode } = _Vue
const _hoisted_1 = _createVNode(\\"p\\", null, [
_createVNode(\\"span\\"),
_createVNode(\\"span\\")
])
], -1 /* HOISTED */)
return function render(_ctx, _cache) {
with (_ctx) {
const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return function render() {
with (this) {
const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return (_openBlock(), _createBlock(\\"div\\", null, [
_hoisted_1
]))
@ -39,16 +39,16 @@ return function render() {
exports[`compiler: hoistStatic transform hoist nested static tree with comments 1`] = `
"const _Vue = Vue
const { createVNode: _createVNode, createCommentVNode: _createCommentVNode } = Vue
const { createVNode: _createVNode, createCommentVNode: _createCommentVNode } = _Vue
const _hoisted_1 = _createVNode(\\"div\\", null, [
_createCommentVNode(\\"comment\\")
])
], -1 /* HOISTED */)
return function render(_ctx, _cache) {
with (_ctx) {
const { createCommentVNode: _createCommentVNode, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return function render() {
with (this) {
const { createCommentVNode: _createCommentVNode, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return (_openBlock(), _createBlock(\\"div\\", null, [
_hoisted_1
]))
@ -58,15 +58,15 @@ return function render() {
exports[`compiler: hoistStatic transform hoist siblings with common non-hoistable parent 1`] = `
"const _Vue = Vue
const { createVNode: _createVNode } = Vue
const { createVNode: _createVNode } = _Vue
const _hoisted_1 = _createVNode(\\"span\\")
const _hoisted_2 = _createVNode(\\"div\\")
const _hoisted_1 = _createVNode(\\"span\\", null, null, -1 /* HOISTED */)
const _hoisted_2 = _createVNode(\\"div\\", null, null, -1 /* HOISTED */)
return function render(_ctx, _cache) {
with (_ctx) {
const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return function render() {
with (this) {
const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return (_openBlock(), _createBlock(\\"div\\", null, [
_hoisted_1,
_hoisted_2
@ -77,14 +77,14 @@ return function render() {
exports[`compiler: hoistStatic transform hoist simple element 1`] = `
"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(_ctx, _cache) {
with (_ctx) {
const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return function render() {
with (this) {
const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return (_openBlock(), _createBlock(\\"div\\", null, [
_hoisted_1
]))
@ -94,18 +94,18 @@ return function render() {
exports[`compiler: hoistStatic transform hoist static props for elements with directives 1`] = `
"const _Vue = Vue
const { createVNode: _createVNode } = Vue
const { createVNode: _createVNode } = _Vue
const _hoisted_1 = { id: \\"foo\\" }
return function render() {
with (this) {
const { createVNode: _createVNode, withDirectives: _withDirectives, resolveDirective: _resolveDirective, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { resolveDirective: _resolveDirective, createVNode: _createVNode, withDirectives: _withDirectives, openBlock: _openBlock, createBlock: _createBlock } = _Vue
const _directive_foo = _resolveDirective(\\"foo\\")
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]
])
]))
@ -115,16 +115,16 @@ return function render() {
exports[`compiler: hoistStatic transform hoist static props for elements with dynamic text children 1`] = `
"const _Vue = Vue
const { createVNode: _createVNode } = Vue
const { createVNode: _createVNode } = _Vue
const _hoisted_1 = { id: \\"foo\\" }
return function render() {
with (this) {
const { toString: _toString, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { toDisplayString: _toDisplayString, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return (_openBlock(), _createBlock(\\"div\\", null, [
_createVNode(\\"div\\", _hoisted_1, _toString(hello), 1 /* TEXT */)
_createVNode(\\"div\\", _hoisted_1, _toDisplayString(hello), 1 /* TEXT */)
]))
}
}"
@ -132,16 +132,16 @@ return function render() {
exports[`compiler: hoistStatic transform hoist static props for elements with unhoistable children 1`] = `
"const _Vue = Vue
const { createVNode: _createVNode } = Vue
const { createVNode: _createVNode } = _Vue
const _hoisted_1 = { id: \\"foo\\" }
return function render() {
with (this) {
const { resolveComponent: _resolveComponent, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { resolveComponent: _resolveComponent, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
const _component_Comp = _resolveComponent(\\"Comp\\")
return (_openBlock(), _createBlock(\\"div\\", null, [
_createVNode(\\"div\\", _hoisted_1, [
_createVNode(_component_Comp)
@ -153,16 +153,16 @@ return function render() {
exports[`compiler: hoistStatic transform prefixIdentifiers hoist class with static object value 1`] = `
"const _Vue = Vue
const { createVNode: _createVNode } = Vue
const { createVNode: _createVNode } = _Vue
const _hoisted_1 = { class: { foo: true } }
return function render() {
with (this) {
const { toString: _toString, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { toDisplayString: _toDisplayString, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return (_openBlock(), _createBlock(\\"div\\", null, [
_createVNode(\\"span\\", _hoisted_1, _toString(_ctx.bar), 1 /* TEXT */)
_createVNode(\\"span\\", _hoisted_1, _toDisplayString(_ctx.bar), 1 /* TEXT */)
]))
}
}"
@ -170,19 +170,14 @@ return function render() {
exports[`compiler: hoistStatic transform prefixIdentifiers hoist nested static tree with static interpolation 1`] = `
"const _Vue = Vue
const { createVNode: _createVNode } = Vue
const { createVNode: _createVNode } = _Vue
const _hoisted_1 = _createVNode(\\"span\\", null, [
\\"foo \\",
_toString(1),
\\" \\",
_toString(true)
])
const _hoisted_1 = _createVNode(\\"span\\", null, \\"foo \\" + _toDisplayString(1) + \\" \\" + _toDisplayString(true), -1 /* HOISTED */)
return function render(_ctx, _cache) {
with (_ctx) {
const { toDisplayString: _toDisplayString, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return function render() {
with (this) {
const { toString: _toString, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return (_openBlock(), _createBlock(\\"div\\", null, [
_hoisted_1
]))
@ -192,14 +187,14 @@ return function render() {
exports[`compiler: hoistStatic transform prefixIdentifiers hoist nested static tree with static prop value 1`] = `
"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(_ctx, _cache) {
with (_ctx) {
const { toDisplayString: _toDisplayString, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return function render() {
with (this) {
const { toString: _toString, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return (_openBlock(), _createBlock(\\"div\\", null, [
_hoisted_1
]))
@ -208,14 +203,12 @@ return function render() {
`;
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() {
const _ctx = this
const _cache = _ctx.$cache
return (openBlock(), createBlock(\\"div\\", null, [
createVNode(\\"div\\", null, [
createVNode(\\"div\\", {
export function render(_ctx, _cache) {
return (_openBlock(), _createBlock(\\"div\\", null, [
_createVNode(\\"div\\", null, [
_createVNode(\\"div\\", {
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`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { renderList: _renderList, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, toString: _toString, createVNode: _createVNode } = _Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, toDisplayString: _toDisplayString, createVNode: _createVNode } = _Vue
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, [
_createVNode(\\"span\\", null, _toString(o + 'foo'), 1 /* TEXT */)
_createVNode(\\"span\\", null, _toDisplayString(o + 'foo'), 1 /* TEXT */)
]))
}), 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`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { toString: _toString, resolveComponent: _resolveComponent, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { toDisplayString: _toDisplayString, createTextVNode: _createTextVNode, resolveComponent: _resolveComponent, withCtx: _withCtx, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
const _component_Comp = _resolveComponent(\\"Comp\\")
return (_openBlock(), _createBlock(_component_Comp, null, {
default: ({ foo }) => [_toString(_ctx.foo)],
_compiled: true
default: _withCtx(({ foo }) => [
_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`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { renderList: _renderList, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, toString: _toString, createVNode: _createVNode } = _Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, toDisplayString: _toDisplayString, createVNode: _createVNode } = _Vue
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, [
_createVNode(\\"span\\", null, _toString(o), 1 /* TEXT */)
_createVNode(\\"span\\", null, _toDisplayString(o), 1 /* TEXT */)
]))
}), 256 /* UNKEYED_FRAGMENT */))
]))
@ -279,12 +274,12 @@ return function render() {
exports[`compiler: hoistStatic transform should NOT hoist components 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { resolveComponent: _resolveComponent, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { resolveComponent: _resolveComponent, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
const _component_Comp = _resolveComponent(\\"Comp\\")
return (_openBlock(), _createBlock(\\"div\\", null, [
_createVNode(_component_Comp)
]))
@ -295,12 +290,12 @@ return function render() {
exports[`compiler: hoistStatic transform should NOT hoist element with dynamic key 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { openBlock: _openBlock, createBlock: _createBlock, createVNode: _createVNode } = _Vue
return (_openBlock(), _createBlock(\\"div\\", null, [
_createVNode(\\"div\\", { key: foo })
(_openBlock(), _createBlock(\\"div\\", { key: foo }))
]))
}
}"
@ -309,10 +304,10 @@ return function render() {
exports[`compiler: hoistStatic transform should NOT hoist element with dynamic props 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return (_openBlock(), _createBlock(\\"div\\", null, [
_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`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return (_openBlock(), _createBlock(\\"div\\", null, [
_createVNode(\\"div\\", { ref: foo }, null, 32 /* NEED_PATCH */)
_createVNode(\\"div\\", { ref: foo }, null, 512 /* NEED_PATCH */)
]))
}
}"
@ -337,10 +332,10 @@ return function render() {
exports[`compiler: hoistStatic transform should NOT hoist root node 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return (_openBlock(), _createBlock(\\"div\\"))
}
}"
@ -348,17 +343,17 @@ return function render() {
exports[`compiler: hoistStatic transform should hoist v-for children if static 1`] = `
"const _Vue = Vue
const { createVNode: _createVNode } = Vue
const { createVNode: _createVNode } = _Vue
const _hoisted_1 = { id: \\"foo\\" }
const _hoisted_2 = _createVNode(\\"span\\")
const _hoisted_2 = _createVNode(\\"span\\", null, null, -1 /* HOISTED */)
return function render(_ctx, _cache) {
with (_ctx) {
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, createVNode: _createVNode } = _Vue
return function render() {
with (this) {
const { renderList: _renderList, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, createVNode: _createVNode } = _Vue
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, [
_hoisted_2
]))
@ -370,24 +365,24 @@ return function render() {
exports[`compiler: hoistStatic transform should hoist v-if props/children if static 1`] = `
"const _Vue = Vue
const { createVNode: _createVNode, createCommentVNode: _createCommentVNode } = Vue
const { createVNode: _createVNode, createCommentVNode: _createCommentVNode } = _Vue
const _hoisted_1 = {
key: 0,
id: \\"foo\\"
}
const _hoisted_2 = _createVNode(\\"span\\")
const _hoisted_2 = _createVNode(\\"span\\", null, null, -1 /* HOISTED */)
return function render(_ctx, _cache) {
with (_ctx) {
const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock, createCommentVNode: _createCommentVNode } = _Vue
return function render() {
with (this) {
const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, createCommentVNode: _createCommentVNode } = _Vue
return (_openBlock(), _createBlock(\\"div\\", null, [
(_openBlock(), ok
? _createBlock(\\"div\\", _hoisted_1, [
ok
? (_openBlock(), _createBlock(\\"div\\", _hoisted_1, [
_hoisted_2
])
: _createCommentVNode(\\"v-if\\", true))
]))
: _createCommentVNode(\\"v-if\\", true)
]))
}
}"

View File

@ -3,11 +3,11 @@
exports[`compiler: transform text <template v-for> 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { renderList: _renderList, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, createTextVNode: _createTextVNode } = _Vue
return (_openBlock(false), _createBlock(_Fragment, null, _renderList(list, (i) => {
return function render(_ctx, _cache) {
with (_ctx) {
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, createTextVNode: _createTextVNode } = _Vue
return (_openBlock(true), _createBlock(_Fragment, null, _renderList(list, (i) => {
return (_openBlock(), _createBlock(_Fragment, null, [
_createTextVNode(\\"foo\\")
], 64 /* STABLE_FRAGMENT */))
@ -19,11 +19,11 @@ return function render() {
exports[`compiler: transform text consecutive text 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { toString: _toString } = _Vue
return _toString(foo) + \\" bar \\" + _toString(baz)
return function render(_ctx, _cache) {
with (_ctx) {
const { toDisplayString: _toDisplayString } = _Vue
return _toDisplayString(foo) + \\" bar \\" + _toDisplayString(baz)
}
}"
`;
@ -31,13 +31,13 @@ return function render() {
exports[`compiler: transform text consecutive text between elements 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { createVNode: _createVNode, toString: _toString, createTextVNode: _createTextVNode, createBlock: _createBlock, Fragment: _Fragment, openBlock: _openBlock } = _Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { createVNode: _createVNode, toDisplayString: _toDisplayString, createTextVNode: _createTextVNode, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return (_openBlock(), _createBlock(_Fragment, null, [
_createVNode(\\"div\\"),
_createTextVNode(_toString(foo) + \\" bar \\" + _toString(baz), 1 /* TEXT */),
_createTextVNode(_toDisplayString(foo) + \\" bar \\" + _toDisplayString(baz), 1 /* TEXT */),
_createVNode(\\"div\\")
], 64 /* STABLE_FRAGMENT */))
}
@ -47,13 +47,13 @@ return function render() {
exports[`compiler: transform text consecutive text mixed with elements 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { createVNode: _createVNode, toString: _toString, createTextVNode: _createTextVNode, createBlock: _createBlock, Fragment: _Fragment, openBlock: _openBlock } = _Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { createVNode: _createVNode, toDisplayString: _toDisplayString, createTextVNode: _createTextVNode, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return (_openBlock(), _createBlock(_Fragment, null, [
_createVNode(\\"div\\"),
_createTextVNode(_toString(foo) + \\" bar \\" + _toString(baz), 1 /* TEXT */),
_createTextVNode(_toDisplayString(foo) + \\" bar \\" + _toDisplayString(baz), 1 /* TEXT */),
_createVNode(\\"div\\"),
_createTextVNode(\\"hello\\"),
_createVNode(\\"div\\")
@ -65,11 +65,11 @@ return function render() {
exports[`compiler: transform text no consecutive text 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { toString: _toString } = _Vue
return _toString(foo)
return function render(_ctx, _cache) {
with (_ctx) {
const { toDisplayString: _toDisplayString } = _Vue
return _toDisplayString(foo)
}
}"
`;
@ -77,10 +77,10 @@ return function render() {
exports[`compiler: transform text text between elements (static) 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { createVNode: _createVNode, createTextVNode: _createTextVNode, createBlock: _createBlock, Fragment: _Fragment, openBlock: _openBlock } = _Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { createVNode: _createVNode, createTextVNode: _createTextVNode, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return (_openBlock(), _createBlock(_Fragment, null, [
_createVNode(\\"div\\"),
_createTextVNode(\\"hello\\"),
@ -91,10 +91,9 @@ return function render() {
`;
exports[`compiler: transform text with prefixIdentifiers: true 1`] = `
"const { toString } = Vue
"const { toDisplayString: _toDisplayString } = Vue
return function render() {
const _ctx = this
return toString(_ctx.foo) + \\" bar \\" + toString(_ctx.baz + _ctx.qux)
return function render(_ctx, _cache) {
return _toDisplayString(_ctx.foo) + \\" bar \\" + _toDisplayString(_ctx.baz + _ctx.qux)
}"
`;

View File

@ -3,11 +3,11 @@
exports[`compiler: v-for codegen basic v-for 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { renderList: _renderList, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, createVNode: _createVNode } = _Vue
return (_openBlock(false), _createBlock(_Fragment, null, _renderList(items, (item) => {
return function render(_ctx, _cache) {
with (_ctx) {
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, createVNode: _createVNode } = _Vue
return (_openBlock(true), _createBlock(_Fragment, null, _renderList(items, (item) => {
return (_openBlock(), _createBlock(\\"span\\"))
}), 256 /* UNKEYED_FRAGMENT */))
}
@ -17,11 +17,11 @@ return function render() {
exports[`compiler: v-for codegen keyed template v-for 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { renderList: _renderList, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, createVNode: _createVNode } = _Vue
return (_openBlock(false), _createBlock(_Fragment, null, _renderList(items, (item) => {
return function render(_ctx, _cache) {
with (_ctx) {
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, createVNode: _createVNode } = _Vue
return (_openBlock(true), _createBlock(_Fragment, null, _renderList(items, (item) => {
return (_openBlock(), _createBlock(_Fragment, { key: item }, [
\\"hello\\",
_createVNode(\\"span\\")
@ -34,11 +34,11 @@ return function render() {
exports[`compiler: v-for codegen keyed v-for 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { renderList: _renderList, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, createVNode: _createVNode } = _Vue
return (_openBlock(false), _createBlock(_Fragment, null, _renderList(items, (item) => {
return function render(_ctx, _cache) {
with (_ctx) {
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return (_openBlock(true), _createBlock(_Fragment, null, _renderList(items, (item) => {
return (_openBlock(), _createBlock(\\"span\\", { key: item }))
}), 128 /* KEYED_FRAGMENT */))
}
@ -48,11 +48,11 @@ return function render() {
exports[`compiler: v-for codegen skipped key 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { renderList: _renderList, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, createVNode: _createVNode } = _Vue
return (_openBlock(false), _createBlock(_Fragment, null, _renderList(items, (item, __, index) => {
return function render(_ctx, _cache) {
with (_ctx) {
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, createVNode: _createVNode } = _Vue
return (_openBlock(true), _createBlock(_Fragment, null, _renderList(items, (item, __, index) => {
return (_openBlock(), _createBlock(\\"span\\"))
}), 256 /* UNKEYED_FRAGMENT */))
}
@ -62,11 +62,11 @@ return function render() {
exports[`compiler: v-for codegen skipped value & key 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { renderList: _renderList, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, createVNode: _createVNode } = _Vue
return (_openBlock(false), _createBlock(_Fragment, null, _renderList(items, (_, __, index) => {
return function render(_ctx, _cache) {
with (_ctx) {
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, createVNode: _createVNode } = _Vue
return (_openBlock(true), _createBlock(_Fragment, null, _renderList(items, (_, __, index) => {
return (_openBlock(), _createBlock(\\"span\\"))
}), 256 /* UNKEYED_FRAGMENT */))
}
@ -76,11 +76,11 @@ return function render() {
exports[`compiler: v-for codegen skipped value 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { renderList: _renderList, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, createVNode: _createVNode } = _Vue
return (_openBlock(false), _createBlock(_Fragment, null, _renderList(items, (_, key, index) => {
return function render(_ctx, _cache) {
with (_ctx) {
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, createVNode: _createVNode } = _Vue
return (_openBlock(true), _createBlock(_Fragment, null, _renderList(items, (_, key, index) => {
return (_openBlock(), _createBlock(\\"span\\"))
}), 256 /* UNKEYED_FRAGMENT */))
}
@ -90,11 +90,11 @@ return function render() {
exports[`compiler: v-for codegen template v-for 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { renderList: _renderList, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, createVNode: _createVNode } = _Vue
return (_openBlock(false), _createBlock(_Fragment, null, _renderList(items, (item) => {
return function render(_ctx, _cache) {
with (_ctx) {
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, createVNode: _createVNode } = _Vue
return (_openBlock(true), _createBlock(_Fragment, null, _renderList(items, (item) => {
return (_openBlock(), _createBlock(_Fragment, null, [
\\"hello\\",
_createVNode(\\"span\\")
@ -107,11 +107,11 @@ return function render() {
exports[`compiler: v-for codegen template v-for w/ <slot/> 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { renderList: _renderList, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, renderSlot: _renderSlot } = _Vue
return (_openBlock(false), _createBlock(_Fragment, null, _renderList(items, (item) => {
return function render(_ctx, _cache) {
with (_ctx) {
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, renderSlot: _renderSlot } = _Vue
return (_openBlock(true), _createBlock(_Fragment, null, _renderList(items, (item) => {
return _renderSlot($slots, \\"default\\")
}), 256 /* UNKEYED_FRAGMENT */))
}
@ -121,11 +121,11 @@ return function render() {
exports[`compiler: v-for codegen v-for on <slot/> 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { renderList: _renderList, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, renderSlot: _renderSlot } = _Vue
return (_openBlock(false), _createBlock(_Fragment, null, _renderList(items, (item) => {
return function render(_ctx, _cache) {
with (_ctx) {
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, renderSlot: _renderSlot } = _Vue
return (_openBlock(true), _createBlock(_Fragment, null, _renderList(items, (item) => {
return _renderSlot($slots, \\"default\\")
}), 256 /* UNKEYED_FRAGMENT */))
}
@ -135,16 +135,16 @@ return function render() {
exports[`compiler: v-for codegen v-for on element with custom directive 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { renderList: _renderList, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, createVNode: _createVNode, withDirectives: _withDirectives, resolveDirective: _resolveDirective } = _Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, resolveDirective: _resolveDirective, createVNode: _createVNode, withDirectives: _withDirectives } = _Vue
const _directive_foo = _resolveDirective(\\"foo\\")
return (_openBlock(false), _createBlock(_Fragment, null, _renderList(list, (i) => {
return (_openBlock(), _withDirectives(_createBlock(\\"div\\", null, null, 32 /* NEED_PATCH */), [
return (_openBlock(true), _createBlock(_Fragment, null, _renderList(list, (i) => {
return _withDirectives((_openBlock(), _createBlock(\\"div\\", null, null, 512 /* NEED_PATCH */)), [
[_directive_foo]
]))
])
}), 256 /* UNKEYED_FRAGMENT */))
}
}"
@ -153,15 +153,15 @@ return function render() {
exports[`compiler: v-for codegen v-if + v-for 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { openBlock: _openBlock, renderList: _renderList, createBlock: _createBlock, Fragment: _Fragment, createVNode: _createVNode, createCommentVNode: _createCommentVNode } = _Vue
return (_openBlock(), ok
? _createBlock(_Fragment, { key: 0 }, _renderList(list, (i) => {
return function render(_ctx, _cache) {
with (_ctx) {
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, createVNode: _createVNode, createCommentVNode: _createCommentVNode } = _Vue
return ok
? (_openBlock(true), _createBlock(_Fragment, { key: 0 }, _renderList(list, (i) => {
return (_openBlock(), _createBlock(\\"div\\"))
}), 256 /* UNKEYED_FRAGMENT */)
: _createCommentVNode(\\"v-if\\", true))
}), 256 /* UNKEYED_FRAGMENT */))
: _createCommentVNode(\\"v-if\\", true)
}
}"
`;
@ -169,11 +169,11 @@ return function render() {
exports[`compiler: v-for codegen value + key + index 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { renderList: _renderList, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, createVNode: _createVNode } = _Vue
return (_openBlock(false), _createBlock(_Fragment, null, _renderList(items, (item, key, index) => {
return function render(_ctx, _cache) {
with (_ctx) {
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, createVNode: _createVNode } = _Vue
return (_openBlock(true), _createBlock(_Fragment, null, _renderList(items, (item, key, index) => {
return (_openBlock(), _createBlock(\\"span\\"))
}), 256 /* UNKEYED_FRAGMENT */))
}

View File

@ -3,13 +3,13 @@
exports[`compiler: v-if codegen basic v-if 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, createCommentVNode: _createCommentVNode } = _Vue
return (_openBlock(), ok
? _createBlock(\\"div\\", { key: 0 })
: _createCommentVNode(\\"v-if\\", true))
return function render(_ctx, _cache) {
with (_ctx) {
const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock, createCommentVNode: _createCommentVNode } = _Vue
return ok
? (_openBlock(), _createBlock(\\"div\\", { key: 0 }))
: _createCommentVNode(\\"v-if\\", true)
}
}"
`;
@ -17,17 +17,17 @@ return function render() {
exports[`compiler: v-if codegen template v-if 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { openBlock: _openBlock, createVNode: _createVNode, Fragment: _Fragment, createBlock: _createBlock, createCommentVNode: _createCommentVNode } = _Vue
return (_openBlock(), ok
? _createBlock(_Fragment, { key: 0 }, [
return function render(_ctx, _cache) {
with (_ctx) {
const { createVNode: _createVNode, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, createCommentVNode: _createCommentVNode } = _Vue
return ok
? (_openBlock(), _createBlock(_Fragment, { key: 0 }, [
_createVNode(\\"div\\"),
\\"hello\\",
_createVNode(\\"p\\")
])
: _createCommentVNode(\\"v-if\\", true))
], 64 /* STABLE_FRAGMENT */))
: _createCommentVNode(\\"v-if\\", true)
}
}"
`;
@ -35,13 +35,13 @@ return function render() {
exports[`compiler: v-if codegen template v-if w/ single <slot/> child 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { openBlock: _openBlock, renderSlot: _renderSlot, createCommentVNode: _createCommentVNode } = _Vue
return (_openBlock(), ok
return function render(_ctx, _cache) {
with (_ctx) {
const { renderSlot: _renderSlot, createCommentVNode: _createCommentVNode } = _Vue
return ok
? _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`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, createCommentVNode: _createCommentVNode } = _Vue
return (_openBlock(), ok
? _createBlock(\\"div\\", { key: 0 })
: _createBlock(\\"p\\", { key: 1 }))
return function render(_ctx, _cache) {
with (_ctx) {
const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock, createCommentVNode: _createCommentVNode } = _Vue
return ok
? (_openBlock(), _createBlock(\\"div\\", { key: 0 }))
: (_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`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, createCommentVNode: _createCommentVNode, Fragment: _Fragment } = _Vue
return (_openBlock(), ok
? _createBlock(\\"div\\", { key: 0 })
return function render(_ctx, _cache) {
with (_ctx) {
const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock, createCommentVNode: _createCommentVNode, Fragment: _Fragment } = _Vue
return ok
? (_openBlock(), _createBlock(\\"div\\", { key: 0 }))
: orNot
? _createBlock(\\"p\\", { key: 1 })
: _createBlock(_Fragment, { key: 2 }, [\\"fine\\"]))
? (_openBlock(), _createBlock(\\"p\\", { key: 1 }))
: (_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`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, createCommentVNode: _createCommentVNode } = _Vue
return (_openBlock(), ok
? _createBlock(\\"div\\", { key: 0 })
return function render(_ctx, _cache) {
with (_ctx) {
const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock, createCommentVNode: _createCommentVNode } = _Vue
return ok
? (_openBlock(), _createBlock(\\"div\\", { key: 0 }))
: orNot
? _createBlock(\\"p\\", { key: 1 })
: _createCommentVNode(\\"v-if\\", true))
? (_openBlock(), _createBlock(\\"p\\", { key: 1 }))
: _createCommentVNode(\\"v-if\\", true)
}
}"
`;
@ -95,13 +95,27 @@ return function render() {
exports[`compiler: v-if codegen v-if on <slot/> 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { openBlock: _openBlock, renderSlot: _renderSlot, createCommentVNode: _createCommentVNode } = _Vue
return (_openBlock(), ok
return function render(_ctx, _cache) {
with (_ctx) {
const { renderSlot: _renderSlot, createCommentVNode: _createCommentVNode } = _Vue
return ok
? _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)
}
}"
`;

View File

@ -1,11 +1,10 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
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() {
const _ctx = this
return (openBlock(), createBlock(\\"input\\", {
export function render(_ctx, _cache) {
return (_openBlock(), _createBlock(\\"input\\", {
modelValue: _ctx.model[_ctx.index],
\\"onUpdate:modelValue\\": $event => (_ctx.model[_ctx.index] = $event)
}, null, 8 /* PROPS */, [\\"modelValue\\", \\"onUpdate:modelValue\\"]))
@ -15,10 +14,10 @@ export default function render() {
exports[`compiler: transform v-model compound expression 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return (_openBlock(), _createBlock(\\"input\\", {
modelValue: model[index],
\\"onUpdate:modelValue\\": $event => (model[index] = $event)
@ -28,11 +27,10 @@ return function render() {
`;
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() {
const _ctx = this
return (openBlock(), createBlock(\\"input\\", {
export function render(_ctx, _cache) {
return (_openBlock(), _createBlock(\\"input\\", {
modelValue: _ctx.model,
\\"onUpdate:modelValue\\": $event => (_ctx.model = $event)
}, null, 8 /* PROPS */, [\\"modelValue\\", \\"onUpdate:modelValue\\"]))
@ -42,10 +40,10 @@ export default function render() {
exports[`compiler: transform v-model simple exprssion 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return (_openBlock(), _createBlock(\\"input\\", {
modelValue: model,
\\"onUpdate:modelValue\\": $event => (model = $event)
@ -57,24 +55,23 @@ return function render() {
exports[`compiler: transform v-model with argument 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return (_openBlock(), _createBlock(\\"input\\", {
value: model,
\\"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`] = `
"import { createVNode, createBlock, openBlock } from \\"vue\\"
"import { createVNode as _createVNode, openBlock as _openBlock, createBlock as _createBlock } from \\"vue\\"
export default function render() {
const _ctx = this
return (openBlock(), createBlock(\\"input\\", {
export function render(_ctx, _cache) {
return (_openBlock(), _createBlock(\\"input\\", {
[_ctx.value]: _ctx.model,
[\\"onUpdate:\\" + _ctx.value]: $event => (_ctx.model = $event)
}, null, 16 /* FULL_PROPS */))
@ -84,10 +81,10 @@ export default function render() {
exports[`compiler: transform v-model with dynamic argument 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return (_openBlock(), _createBlock(\\"input\\", {
[value]: model,
[\\"onUpdate:\\" + value]: $event => (model = $event)

View File

@ -3,11 +3,10 @@
exports[`compiler: v-once transform as root node 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
return function render(_ctx, _cache) {
with (_ctx) {
const { setBlockTracking: _setBlockTracking, createVNode: _createVNode } = _Vue
const _cache = $cache
return _cache[1] || (
_setBlockTracking(-1),
_cache[1] = _createVNode(\\"div\\", { id: foo }, null, 8 /* PROPS */, [\\"id\\"]),
@ -21,13 +20,12 @@ return function render() {
exports[`compiler: v-once transform on component 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { setBlockTracking: _setBlockTracking, resolveComponent: _resolveComponent, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
const _cache = $cache
return function render(_ctx, _cache) {
with (_ctx) {
const { setBlockTracking: _setBlockTracking, resolveComponent: _resolveComponent, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
const _component_Comp = _resolveComponent(\\"Comp\\")
return (_openBlock(), _createBlock(\\"div\\", null, [
_cache[1] || (
_setBlockTracking(-1),
@ -43,11 +41,10 @@ return function render() {
exports[`compiler: v-once transform on nested plain element 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { setBlockTracking: _setBlockTracking, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
const _cache = $cache
return function render(_ctx, _cache) {
with (_ctx) {
const { setBlockTracking: _setBlockTracking, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return (_openBlock(), _createBlock(\\"div\\", null, [
_cache[1] || (
_setBlockTracking(-1),
@ -63,11 +60,10 @@ return function render() {
exports[`compiler: v-once transform on slot outlet 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { setBlockTracking: _setBlockTracking, renderSlot: _renderSlot, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
const _cache = $cache
return function render(_ctx, _cache) {
with (_ctx) {
const { setBlockTracking: _setBlockTracking, renderSlot: _renderSlot, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return (_openBlock(), _createBlock(\\"div\\", null, [
_cache[1] || (
_setBlockTracking(-1),
@ -83,11 +79,10 @@ return function render() {
exports[`compiler: v-once transform with hoistStatic: true 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { setBlockTracking: _setBlockTracking, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
const _cache = $cache
return function render(_ctx, _cache) {
with (_ctx) {
const { setBlockTracking: _setBlockTracking, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return (_openBlock(), _createBlock(\\"div\\", null, [
_cache[1] || (
_setBlockTracking(-1),

View File

@ -1,111 +1,93 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
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() {
const _ctx = this
const _component_Comp = resolveComponent(\\"Comp\\")
return (openBlock(), createBlock(_component_Comp, null, {
[_ctx.one]: ({ foo }) => [toString(foo), toString(_ctx.bar)],
[_ctx.two]: ({ bar }) => [toString(_ctx.foo), toString(bar)],
_compiled: true
}, 512 /* DYNAMIC_SLOTS */))
}"
`;
return function render(_ctx, _cache) {
const _component_Comp = _resolveComponent(\\"Comp\\")
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
}))
return (_openBlock(), _createBlock(_component_Comp, null, {
[_ctx.one]: _withCtx(({ foo }) => [_toDisplayString(foo), _toDisplayString(_ctx.bar)]),
[_ctx.two]: _withCtx(({ bar }) => [_toDisplayString(_ctx.foo), _toDisplayString(bar)]),
_: 1
}, 1024 /* DYNAMIC_SLOTS */))
}"
`;
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() {
const _ctx = this
const _component_Comp = resolveComponent(\\"Comp\\")
return (openBlock(), createBlock(_component_Comp, null, {
default: () => [
createVNode(\\"div\\")
],
_compiled: true
return function render(_ctx, _cache) {
const _component_Comp = _resolveComponent(\\"Comp\\")
return (_openBlock(), _createBlock(_component_Comp, null, {
default: _withCtx(() => [
_createVNode(\\"div\\")
]),
_: 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() {
const _ctx = this
const _component_Comp = resolveComponent(\\"Comp\\")
return (openBlock(), createBlock(_component_Comp, null, createSlots({ _compiled: true }, [
renderList(_ctx.list, (name) => {
return function render(_ctx, _cache) {
const _component_Comp = _resolveComponent(\\"Comp\\")
return (_openBlock(), _createBlock(_component_Comp, null, _createSlots({ _: 1 }, [
_renderList(_ctx.list, (name) => {
return {
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`] = `
"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() {
const _ctx = this
const _component_Comp = resolveComponent(\\"Comp\\")
return (openBlock(), createBlock(_component_Comp, null, createSlots({ _compiled: true }, [
return function render(_ctx, _cache) {
const _component_Comp = _resolveComponent(\\"Comp\\")
return (_openBlock(), _createBlock(_component_Comp, null, _createSlots({ _: 1 }, [
(_ctx.ok)
? {
name: \\"one\\",
fn: (props) => [toString(props)]
fn: _withCtx((props) => [_toDisplayString(props)])
}
: undefined
]), 512 /* DYNAMIC_SLOTS */))
]), 1024 /* DYNAMIC_SLOTS */))
}"
`;
exports[`compiler: transform component slots named slot with v-if + v-else-if + v-else 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { resolveComponent: _resolveComponent, createSlots: _createSlots, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { resolveComponent: _resolveComponent, withCtx: _withCtx, createSlots: _createSlots, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
const _component_Comp = _resolveComponent(\\"Comp\\")
return (_openBlock(), _createBlock(_component_Comp, null, _createSlots({ _compiled: true }, [
return (_openBlock(), _createBlock(_component_Comp, null, _createSlots({ _: 1 }, [
ok
? {
name: \\"one\\",
fn: () => [\\"foo\\"]
fn: _withCtx(() => [\\"foo\\"])
}
: orNot
? {
name: \\"two\\",
fn: (props) => [\\"bar\\"]
fn: _withCtx((props) => [\\"bar\\"])
}
: {
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`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { resolveComponent: _resolveComponent, createSlots: _createSlots, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { resolveComponent: _resolveComponent, withCtx: _withCtx, createSlots: _createSlots, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
const _component_Comp = _resolveComponent(\\"Comp\\")
return (_openBlock(), _createBlock(_component_Comp, null, _createSlots({ _compiled: true }, [
return (_openBlock(), _createBlock(_component_Comp, null, _createSlots({ _: 1 }, [
ok
? {
name: \\"one\\",
fn: () => [\\"hello\\"]
fn: _withCtx(() => [\\"hello\\"])
}
: undefined
]), 512 /* DYNAMIC_SLOTS */))
]), 1024 /* DYNAMIC_SLOTS */))
}
}"
`;
exports[`compiler: transform component slots named slots 1`] = `
"const { toString, resolveComponent, createVNode, createBlock, openBlock } = Vue
exports[`compiler: transform component slots named slots w/ implicit default slot 1`] = `
"const _Vue = Vue
return function render() {
const _ctx = this
const _component_Comp = resolveComponent(\\"Comp\\")
return (openBlock(), createBlock(_component_Comp, null, {
one: ({ foo }) => [toString(foo), toString(_ctx.bar)],
two: ({ bar }) => [toString(_ctx.foo), toString(bar)],
_compiled: true
}))
return function render(_ctx, _cache) {
with (_ctx) {
const { createVNode: _createVNode, resolveComponent: _resolveComponent, withCtx: _withCtx, openBlock: _openBlock, createBlock: _createBlock } = _Vue
const _component_Comp = _resolveComponent(\\"Comp\\")
return (_openBlock(), _createBlock(_component_Comp, null, {
one: _withCtx(() => [\\"foo\\"]),
default: _withCtx(() => [
\\"bar\\",
_createVNode(\\"span\\")
]),
_: 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() {
const _ctx = this
const _component_Inner = resolveComponent(\\"Inner\\")
const _component_Comp = resolveComponent(\\"Comp\\")
return (openBlock(), createBlock(_component_Comp, null, {
default: ({ foo }) => [
createVNode(_component_Inner, null, {
default: ({ bar }) => [toString(foo), toString(bar), toString(_ctx.baz)],
_compiled: true
}, 512 /* DYNAMIC_SLOTS */),
return function render(_ctx, _cache) {
const _component_Inner = _resolveComponent(\\"Inner\\")
const _component_Comp = _resolveComponent(\\"Comp\\")
return (_openBlock(), _createBlock(_component_Comp, null, {
default: _withCtx(({ foo }) => [
_createVNode(_component_Inner, null, {
default: _withCtx(({ bar }) => [_toDisplayString(foo), _toDisplayString(bar), _toDisplayString(_ctx.baz)]),
_: 1
}, 1024 /* DYNAMIC_SLOTS */),
\\" \\",
toString(foo),
toString(_ctx.bar),
toString(_ctx.baz)
],
_compiled: true
_toDisplayString(foo),
_toDisplayString(_ctx.bar),
_toDisplayString(_ctx.baz)
]),
_: 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
}))
}"
`;

View File

@ -1,18 +1,15 @@
import {
parse,
baseParse as parse,
transform,
NodeTypes,
generate,
CompilerOptions
CompilerOptions,
VNodeCall,
IfNode,
ElementNode,
ForNode
} from '../../src'
import {
OPEN_BLOCK,
CREATE_BLOCK,
CREATE_VNODE,
WITH_DIRECTIVES,
FRAGMENT,
RENDER_LIST
} from '../../src/runtimeHelpers'
import { FRAGMENT, RENDER_LIST, CREATE_TEXT } from '../../src/runtimeHelpers'
import { transformElement } from '../../src/transforms/transformElement'
import { transformExpression } from '../../src/transforms/transformExpression'
import { transformIf } from '../../src/transforms/vIf'
@ -20,6 +17,7 @@ import { transformFor } from '../../src/transforms/vFor'
import { transformBind } from '../../src/transforms/vBind'
import { transformOn } from '../../src/transforms/vOn'
import { createObjectMatcher, genFlagText } from '../testUtils'
import { transformText } from '../../src/transforms/transformText'
import { PatchFlags } from '@vue/shared'
function transformWithHoist(template: string, options: CompilerOptions = {}) {
@ -30,7 +28,8 @@ function transformWithHoist(template: string, options: CompilerOptions = {}) {
transformIf,
transformFor,
...(options.prefixIdentifiers ? [transformExpression] : []),
transformElement
transformElement,
transformText
],
directiveTransforms: {
on: transformOn,
@ -39,56 +38,43 @@ function transformWithHoist(template: string, options: CompilerOptions = {}) {
...options
})
expect(ast.codegenNode).toMatchObject({
type: NodeTypes.JS_SEQUENCE_EXPRESSION,
expressions: [
{
type: NodeTypes.JS_CALL_EXPRESSION,
callee: OPEN_BLOCK
},
{
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_BLOCK
}
]
type: NodeTypes.VNODE_CALL,
isBlock: true
})
return {
root: ast,
args: (ast.codegenNode as any).expressions[1].arguments
}
return ast
}
describe('compiler: hoistStatic transform', () => {
test('should NOT hoist root node', () => {
// 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
const { root, args } = transformWithHoist(`<div/>`)
const root = transformWithHoist(`<div/>`)
expect(root.hoists.length).toBe(0)
expect(args).toEqual([`"div"`])
expect(root.codegenNode).toMatchObject({
tag: `"div"`
})
expect(generate(root).code).toMatchSnapshot()
})
test('hoist simple element', () => {
const { root, args } = transformWithHoist(
const root = transformWithHoist(
`<div><span class="inline">hello</span></div>`
)
expect(root.hoists).toMatchObject([
{
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_VNODE,
arguments: [
`"span"`,
createObjectMatcher({ class: 'inline' }),
{
type: NodeTypes.TEXT,
content: `hello`
}
]
type: NodeTypes.VNODE_CALL,
tag: `"span"`,
props: createObjectMatcher({ class: 'inline' }),
children: {
type: NodeTypes.TEXT,
content: `hello`
}
}
])
expect(args).toMatchObject([
`"div"`,
`null`,
[
expect(root.codegenNode).toMatchObject({
tag: `"div"`,
props: undefined,
children: [
{
type: NodeTypes.ELEMENT,
codegenNode: {
@ -97,29 +83,24 @@ describe('compiler: hoistStatic transform', () => {
}
}
]
])
})
expect(generate(root).code).toMatchSnapshot()
})
test('hoist nested static tree', () => {
const { root, args } = transformWithHoist(
`<div><p><span/><span/></p></div>`
)
const root = transformWithHoist(`<div><p><span/><span/></p></div>`)
expect(root.hoists).toMatchObject([
{
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_VNODE,
arguments: [
`"p"`,
`null`,
[
{ type: NodeTypes.ELEMENT, tag: `span` },
{ type: NodeTypes.ELEMENT, tag: `span` }
]
type: NodeTypes.VNODE_CALL,
tag: `"p"`,
props: undefined,
children: [
{ type: NodeTypes.ELEMENT, tag: `span` },
{ type: NodeTypes.ELEMENT, tag: `span` }
]
}
])
expect(args[2]).toMatchObject([
expect((root.codegenNode as VNodeCall).children).toMatchObject([
{
type: NodeTypes.ELEMENT,
codegenNode: {
@ -132,21 +113,16 @@ describe('compiler: hoistStatic transform', () => {
})
test('hoist nested static tree with comments', () => {
const { root, args } = transformWithHoist(
`<div><div><!--comment--></div></div>`
)
const root = transformWithHoist(`<div><div><!--comment--></div></div>`)
expect(root.hoists).toMatchObject([
{
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_VNODE,
arguments: [
`"div"`,
`null`,
[{ type: NodeTypes.COMMENT, content: `comment` }]
]
type: NodeTypes.VNODE_CALL,
tag: `"div"`,
props: undefined,
children: [{ type: NodeTypes.COMMENT, content: `comment` }]
}
])
expect(args[2]).toMatchObject([
expect((root.codegenNode as VNodeCall).children).toMatchObject([
{
type: NodeTypes.ELEMENT,
codegenNode: {
@ -159,20 +135,18 @@ describe('compiler: hoistStatic transform', () => {
})
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([
{
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_VNODE,
arguments: [`"span"`]
type: NodeTypes.VNODE_CALL,
tag: `"span"`
},
{
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_VNODE,
arguments: [`"div"`]
type: NodeTypes.VNODE_CALL,
tag: `"div"`
}
])
expect(args[2]).toMatchObject([
expect((root.codegenNode as VNodeCall).children).toMatchObject([
{
type: NodeTypes.ELEMENT,
codegenNode: {
@ -192,14 +166,14 @@ describe('compiler: hoistStatic transform', () => {
})
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(args[2]).toMatchObject([
expect((root.codegenNode as VNodeCall).children).toMatchObject([
{
type: NodeTypes.ELEMENT,
codegenNode: {
callee: CREATE_VNODE,
arguments: [`_component_Comp`]
type: NodeTypes.VNODE_CALL,
tag: `_component_Comp`
}
}
])
@ -207,22 +181,20 @@ describe('compiler: hoistStatic transform', () => {
})
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(args[2]).toMatchObject([
expect((root.codegenNode as VNodeCall).children).toMatchObject([
{
type: NodeTypes.ELEMENT,
codegenNode: {
callee: CREATE_VNODE,
arguments: [
`"div"`,
createObjectMatcher({
id: `[foo]`
}),
`null`,
genFlagText(PatchFlags.PROPS),
`["id"]`
]
type: NodeTypes.VNODE_CALL,
tag: `"div"`,
props: createObjectMatcher({
id: `[foo]`
}),
children: undefined,
patchFlag: genFlagText(PatchFlags.PROPS),
dynamicProps: `["id"]`
}
}
])
@ -230,19 +202,19 @@ describe('compiler: hoistStatic transform', () => {
})
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).toMatchObject([
{
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_VNODE,
arguments: [`"div"`, createObjectMatcher({ key: 'foo' })]
type: NodeTypes.VNODE_CALL,
tag: `"div"`,
props: createObjectMatcher({ key: 'foo' })
}
])
expect(args).toMatchObject([
`"div"`,
`null`,
[
expect(root.codegenNode).toMatchObject({
tag: `"div"`,
props: undefined,
children: [
{
type: NodeTypes.ELEMENT,
codegenNode: {
@ -251,24 +223,22 @@ describe('compiler: hoistStatic transform', () => {
}
}
]
])
})
expect(generate(root).code).toMatchSnapshot()
})
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(args[2]).toMatchObject([
expect((root.codegenNode as VNodeCall).children).toMatchObject([
{
type: NodeTypes.ELEMENT,
codegenNode: {
callee: CREATE_VNODE,
arguments: [
`"div"`,
createObjectMatcher({
key: `[foo]`
})
]
type: NodeTypes.VNODE_CALL,
tag: `"div"`,
props: createObjectMatcher({
key: `[foo]`
})
}
}
])
@ -276,21 +246,19 @@ describe('compiler: hoistStatic transform', () => {
})
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(args[2]).toMatchObject([
expect((root.codegenNode as VNodeCall).children).toMatchObject([
{
type: NodeTypes.ELEMENT,
codegenNode: {
callee: CREATE_VNODE,
arguments: [
`"div"`,
createObjectMatcher({
ref: `[foo]`
}),
`null`,
genFlagText(PatchFlags.NEED_PATCH)
]
type: NodeTypes.VNODE_CALL,
tag: `"div"`,
props: createObjectMatcher({
ref: `[foo]`
}),
children: undefined,
patchFlag: genFlagText(PatchFlags.NEED_PATCH)
}
}
])
@ -298,32 +266,23 @@ describe('compiler: hoistStatic transform', () => {
})
test('hoist static props for elements with directives', () => {
const { root, args } = transformWithHoist(
`<div><div id="foo" v-foo/></div>`
)
const root = transformWithHoist(`<div><div id="foo" v-foo/></div>`)
expect(root.hoists).toMatchObject([createObjectMatcher({ id: 'foo' })])
expect(args[2]).toMatchObject([
expect((root.codegenNode as VNodeCall).children).toMatchObject([
{
type: NodeTypes.ELEMENT,
codegenNode: {
callee: WITH_DIRECTIVES,
arguments: [
{
callee: CREATE_VNODE,
arguments: [
`"div"`,
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: `_hoisted_1`
},
`null`,
genFlagText(PatchFlags.NEED_PATCH)
]
},
{
type: NodeTypes.JS_ARRAY_EXPRESSION
}
]
type: NodeTypes.VNODE_CALL,
tag: `"div"`,
props: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `_hoisted_1`
},
children: undefined,
patchFlag: genFlagText(PatchFlags.NEED_PATCH),
directives: {
type: NodeTypes.JS_ARRAY_EXPRESSION
}
}
}
])
@ -331,21 +290,19 @@ describe('compiler: hoistStatic transform', () => {
})
test('hoist static props for elements with dynamic text children', () => {
const { root, args } = transformWithHoist(
const root = transformWithHoist(
`<div><div id="foo">{{ hello }}</div></div>`
)
expect(root.hoists).toMatchObject([createObjectMatcher({ id: 'foo' })])
expect(args[2]).toMatchObject([
expect((root.codegenNode as VNodeCall).children).toMatchObject([
{
type: NodeTypes.ELEMENT,
codegenNode: {
callee: CREATE_VNODE,
arguments: [
`"div"`,
{ content: `_hoisted_1` },
{ type: NodeTypes.INTERPOLATION },
genFlagText(PatchFlags.TEXT)
]
type: NodeTypes.VNODE_CALL,
tag: `"div"`,
props: { content: `_hoisted_1` },
children: { type: NodeTypes.INTERPOLATION },
patchFlag: genFlagText(PatchFlags.TEXT)
}
}
])
@ -353,20 +310,16 @@ describe('compiler: hoistStatic transform', () => {
})
test('hoist static props for elements with unhoistable children', () => {
const { root, args } = transformWithHoist(
`<div><div id="foo"><Comp/></div></div>`
)
const root = transformWithHoist(`<div><div id="foo"><Comp/></div></div>`)
expect(root.hoists).toMatchObject([createObjectMatcher({ id: 'foo' })])
expect(args[2]).toMatchObject([
expect((root.codegenNode as VNodeCall).children).toMatchObject([
{
type: NodeTypes.ELEMENT,
codegenNode: {
callee: CREATE_VNODE,
arguments: [
`"div"`,
{ content: `_hoisted_1` },
[{ type: NodeTypes.ELEMENT, tag: `Comp` }]
]
type: NodeTypes.VNODE_CALL,
tag: `"div"`,
props: { content: `_hoisted_1` },
children: [{ type: NodeTypes.ELEMENT, tag: `Comp` }]
}
}
])
@ -374,7 +327,7 @@ describe('compiler: hoistStatic transform', () => {
})
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>`
)
expect(root.hoists).toMatchObject([
@ -383,37 +336,31 @@ describe('compiler: hoistStatic transform', () => {
id: 'foo'
}),
{
callee: CREATE_VNODE,
arguments: [`"span"`]
type: NodeTypes.VNODE_CALL,
tag: `"span"`
}
])
expect(args[2][0].codegenNode).toMatchObject({
type: NodeTypes.JS_SEQUENCE_EXPRESSION,
expressions: [
{ callee: OPEN_BLOCK },
{
type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
consequent: {
// blocks should NOT be hoisted
callee: CREATE_BLOCK,
arguments: [
`"div"`,
{ content: `_hoisted_1` },
[
{
codegenNode: { content: `_hoisted_2` }
}
]
]
expect(
((root.children[0] as ElementNode).children[0] as IfNode).codegenNode
).toMatchObject({
type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
consequent: {
// blocks should NOT be hoisted
type: NodeTypes.VNODE_CALL,
tag: `"div"`,
props: { content: `_hoisted_1` },
children: [
{
codegenNode: { content: `_hoisted_2` }
}
}
]
]
}
})
expect(generate(root).code).toMatchSnapshot()
})
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>`
)
expect(root.hoists).toMatchObject([
@ -421,55 +368,58 @@ describe('compiler: hoistStatic transform', () => {
id: 'foo'
}),
{
callee: CREATE_VNODE,
arguments: [`"span"`]
type: NodeTypes.VNODE_CALL,
tag: `"span"`
}
])
const forBlockCodegen = args[2][0].codegenNode
const forBlockCodegen = ((root.children[0] as ElementNode)
.children[0] as ForNode).codegenNode
expect(forBlockCodegen).toMatchObject({
type: NodeTypes.JS_SEQUENCE_EXPRESSION,
expressions: [
{ callee: OPEN_BLOCK },
{
callee: CREATE_BLOCK,
arguments: [
FRAGMENT,
`null`,
{
type: NodeTypes.JS_CALL_EXPRESSION,
callee: RENDER_LIST
},
genFlagText(PatchFlags.UNKEYED_FRAGMENT)
]
}
]
type: NodeTypes.VNODE_CALL,
tag: FRAGMENT,
props: undefined,
children: {
type: NodeTypes.JS_CALL_EXPRESSION,
callee: RENDER_LIST
},
patchFlag: genFlagText(PatchFlags.UNKEYED_FRAGMENT)
})
const innerBlockCodegen =
forBlockCodegen.expressions[1].arguments[2].arguments[1].returns
expect(innerBlockCodegen).toMatchObject({
type: NodeTypes.JS_SEQUENCE_EXPRESSION,
expressions: [
{ callee: OPEN_BLOCK },
const innerBlockCodegen = forBlockCodegen!.children.arguments[1]
expect(innerBlockCodegen.returns).toMatchObject({
type: NodeTypes.VNODE_CALL,
tag: `"div"`,
props: { content: `_hoisted_1` },
children: [
{
callee: CREATE_BLOCK,
arguments: [
`"div"`,
{ content: `_hoisted_1` },
[
{
codegenNode: { content: `_hoisted_2` }
}
]
]
codegenNode: { content: `_hoisted_2` }
}
]
})
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', () => {
test('hoist nested static tree with static interpolation', () => {
const { root, args } = transformWithHoist(
const root = transformWithHoist(
`<div><span>foo {{ 1 }} {{ true }}</span></div>`,
{
prefixIdentifiers: true
@ -477,44 +427,18 @@ describe('compiler: hoistStatic transform', () => {
)
expect(root.hoists).toMatchObject([
{
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_VNODE,
arguments: [
`"span"`,
`null`,
[
{
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
}
}
]
]
type: NodeTypes.VNODE_CALL,
tag: `"span"`,
props: undefined,
children: {
type: NodeTypes.COMPOUND_EXPRESSION
}
}
])
expect(args).toMatchObject([
`"div"`,
`null`,
[
expect(root.codegenNode).toMatchObject({
tag: `"div"`,
props: undefined,
children: [
{
type: NodeTypes.ELEMENT,
codegenNode: {
@ -523,12 +447,12 @@ describe('compiler: hoistStatic transform', () => {
}
}
]
])
})
expect(generate(root).code).toMatchSnapshot()
})
test('hoist nested static tree with static prop value', () => {
const { root, args } = transformWithHoist(
const root = transformWithHoist(
`<div><span :foo="0">{{ 1 }}</span></div>`,
{
prefixIdentifiers: true
@ -537,26 +461,23 @@ describe('compiler: hoistStatic transform', () => {
expect(root.hoists).toMatchObject([
{
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_VNODE,
arguments: [
`"span"`,
createObjectMatcher({ foo: `[0]` }),
{
type: NodeTypes.INTERPOLATION,
content: {
content: `1`,
isStatic: false,
isConstant: true
}
type: NodeTypes.VNODE_CALL,
tag: `"span"`,
props: createObjectMatcher({ foo: `[0]` }),
children: {
type: NodeTypes.INTERPOLATION,
content: {
content: `1`,
isStatic: false,
isConstant: true
}
]
}
}
])
expect(args).toMatchObject([
`"div"`,
`null`,
[
expect(root.codegenNode).toMatchObject({
tag: `"div"`,
props: undefined,
children: [
{
type: NodeTypes.ELEMENT,
codegenNode: {
@ -565,12 +486,12 @@ describe('compiler: hoistStatic transform', () => {
}
}
]
])
})
expect(generate(root).code).toMatchSnapshot()
})
test('hoist class with static object value', () => {
const { root, args } = transformWithHoist(
const root = transformWithHoist(
`<div><span :class="{ foo: true }">{{ bar }}</span></div>`,
{
prefixIdentifiers: true
@ -596,39 +517,37 @@ describe('compiler: hoistStatic transform', () => {
]
}
])
expect(args).toMatchObject([
`"div"`,
`null`,
[
expect(root.codegenNode).toMatchObject({
tag: `"div"`,
props: undefined,
children: [
{
type: NodeTypes.ELEMENT,
codegenNode: {
callee: CREATE_VNODE,
arguments: [
`"span"`,
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: `_hoisted_1`
},
{
type: NodeTypes.INTERPOLATION,
content: {
content: `_ctx.bar`,
isConstant: false,
isStatic: false
}
},
`1 /* TEXT */`
]
type: NodeTypes.VNODE_CALL,
tag: `"span"`,
props: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `_hoisted_1`
},
children: {
type: NodeTypes.INTERPOLATION,
content: {
content: `_ctx.bar`,
isConstant: false,
isStatic: false
}
},
patchFlag: `1 /* TEXT */`
}
}
]
])
})
expect(generate(root).code).toMatchSnapshot()
})
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>`,
{
prefixIdentifiers: true
@ -640,7 +559,7 @@ describe('compiler: hoistStatic transform', () => {
})
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>`,
{
prefixIdentifiers: true
@ -652,7 +571,7 @@ describe('compiler: hoistStatic transform', () => {
})
test('should NOT hoist expressions that refer scope variables (v-slot)', () => {
const { root } = transformWithHoist(
const root = transformWithHoist(
`<Comp v-slot="{ foo }">{{ foo }}</Comp>`,
{
prefixIdentifiers: true
@ -664,7 +583,7 @@ describe('compiler: hoistStatic transform', () => {
})
test('should NOT hoist elements with cached handlers', () => {
const { root } = transformWithHoist(
const root = transformWithHoist(
`<div><div><div @click="foo"/></div></div>`,
{
prefixIdentifiers: true,

View File

@ -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()
})
})

View File

@ -1,24 +1,28 @@
import { CompilerOptions, parse, transform, ErrorCodes } from '../../src'
import {
CompilerOptions,
baseParse as parse,
transform,
ErrorCodes
} from '../../src'
import {
RESOLVE_COMPONENT,
CREATE_VNODE,
MERGE_PROPS,
RESOLVE_DIRECTIVE,
WITH_DIRECTIVES,
TO_HANDLERS,
helperNameMap,
PORTAL,
TELEPORT,
RESOLVE_DYNAMIC_COMPONENT,
SUSPENSE,
KEEP_ALIVE,
BASE_TRANSITION
} from '../../src/runtimeHelpers'
import {
CallExpression,
NodeTypes,
createObjectProperty,
DirectiveNode,
RootNode
RootNode,
VNodeCall
} from '../../src/ast'
import { transformElement } from '../../src/transforms/transformElement'
import { transformStyle } from '../../../compiler-dom/src/transforms/transformStyle'
@ -33,7 +37,7 @@ function parseWithElementTransform(
options: CompilerOptions = {}
): {
root: RootNode
node: CallExpression
node: VNodeCall
} {
// wrap raw template in an extra div so that it doesn't get turned into a
// block as root node
@ -43,8 +47,8 @@ function parseWithElementTransform(
...options
})
const codegenNode = (ast as any).children[0].children[0]
.codegenNode as CallExpression
expect(codegenNode.type).toBe(NodeTypes.JS_CALL_EXPRESSION)
.codegenNode as VNodeCall
expect(codegenNode.type).toBe(NodeTypes.VNODE_CALL)
return {
root: ast,
node: codegenNode
@ -68,63 +72,63 @@ describe('compiler: element transform', () => {
test('static props', () => {
const { node } = parseWithElementTransform(`<div id="foo" class="bar" />`)
expect(node.callee).toBe(CREATE_VNODE)
expect(node.arguments).toMatchObject([
`"div"`,
createObjectMatcher({
expect(node).toMatchObject({
tag: `"div"`,
props: createObjectMatcher({
id: 'foo',
class: 'bar'
})
])
}),
children: undefined
})
})
test('props + children', () => {
const { node } = parseWithElementTransform(`<div id="foo"><span/></div>`)
expect(node.callee).toBe(CREATE_VNODE)
expect(node.arguments).toMatchObject([
`"div"`,
createObjectMatcher({
expect(node).toMatchObject({
tag: `"div"`,
props: createObjectMatcher({
id: 'foo'
}),
[
children: [
{
type: NodeTypes.ELEMENT,
tag: 'span',
codegenNode: {
callee: CREATE_VNODE,
arguments: [`"span"`]
type: NodeTypes.VNODE_CALL,
tag: `"span"`
}
}
]
])
})
})
test('0 placeholder for children with no props', () => {
const { node } = parseWithElementTransform(`<div><span/></div>`)
expect(node.callee).toBe(CREATE_VNODE)
expect(node.arguments).toMatchObject([
`"div"`,
`null`,
[
expect(node).toMatchObject({
tag: `"div"`,
props: undefined,
children: [
{
type: NodeTypes.ELEMENT,
tag: 'span',
codegenNode: {
callee: CREATE_VNODE,
arguments: [`"span"`]
type: NodeTypes.VNODE_CALL,
tag: `"span"`
}
}
]
])
})
})
test('v-bind="obj"', () => {
const { root, node } = parseWithElementTransform(`<div v-bind="obj" />`)
// single v-bind doesn't need mergeProps
expect(root.helpers).not.toContain(MERGE_PROPS)
expect(node.callee).toBe(CREATE_VNODE)
// should directly use `obj` in props position
expect(node.arguments[1]).toMatchObject({
expect(node.props).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION,
content: `obj`
})
@ -135,8 +139,8 @@ describe('compiler: element transform', () => {
`<div id="foo" v-bind="obj" />`
)
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,
callee: MERGE_PROPS,
arguments: [
@ -156,8 +160,8 @@ describe('compiler: element transform', () => {
`<div v-bind="obj" id="foo" />`
)
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,
callee: MERGE_PROPS,
arguments: [
@ -177,8 +181,8 @@ describe('compiler: element transform', () => {
`<div id="foo" v-bind="obj" class="bar" />`
)
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,
callee: MERGE_PROPS,
arguments: [
@ -201,8 +205,8 @@ describe('compiler: element transform', () => {
`<div id="foo" v-on="obj" class="bar" />`
)
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,
callee: MERGE_PROPS,
arguments: [
@ -231,8 +235,8 @@ describe('compiler: element transform', () => {
`<div id="foo" v-on="handlers" v-bind="obj" />`
)
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,
callee: MERGE_PROPS,
arguments: [
@ -259,43 +263,43 @@ describe('compiler: element transform', () => {
test('should handle plain <template> as normal element', () => {
const { node } = parseWithElementTransform(`<template id="foo" />`)
expect(node.callee).toBe(CREATE_VNODE)
expect(node.arguments).toMatchObject([
`"template"`,
createObjectMatcher({
expect(node).toMatchObject({
tag: `"template"`,
props: createObjectMatcher({
id: 'foo'
})
])
})
})
test('should handle <Portal> with normal children', () => {
test('should handle <Teleport> with normal children', () => {
function assert(tag: string) {
const { root, node } = parseWithElementTransform(
`<${tag} target="#foo"><span /></${tag}>`
)
expect(root.components.length).toBe(0)
expect(root.helpers).toContain(PORTAL)
expect(node.callee).toBe(CREATE_VNODE)
expect(node.arguments).toMatchObject([
PORTAL,
createObjectMatcher({
expect(root.helpers).toContain(TELEPORT)
expect(node).toMatchObject({
tag: TELEPORT,
props: createObjectMatcher({
target: '#foo'
}),
[
children: [
{
type: NodeTypes.ELEMENT,
tag: 'span',
codegenNode: {
callee: CREATE_VNODE,
arguments: [`"span"`]
type: NodeTypes.VNODE_CALL,
tag: `"span"`
}
}
]
])
})
}
assert(`portal`)
assert(`Portal`)
assert(`teleport`)
assert(`Teleport`)
})
test('should handle <Suspense>', () => {
@ -305,11 +309,11 @@ describe('compiler: element transform', () => {
)
expect(root.components.length).toBe(0)
expect(root.helpers).toContain(SUSPENSE)
expect(node.callee).toBe(CREATE_VNODE)
expect(node.arguments).toMatchObject([
SUSPENSE,
`null`,
hasFallback
expect(node).toMatchObject({
tag: SUSPENSE,
props: undefined,
children: hasFallback
? createObjectMatcher({
default: {
type: NodeTypes.JS_FUNCTION_EXPRESSION
@ -317,15 +321,15 @@ describe('compiler: element transform', () => {
fallback: {
type: NodeTypes.JS_FUNCTION_EXPRESSION
},
_compiled: `[true]`
_: `[1]`
})
: createObjectMatcher({
default: {
type: NodeTypes.JS_FUNCTION_EXPRESSION
},
_compiled: `[true]`
_: `[1]`
})
])
})
}
assert(`suspense`, `foo`)
@ -339,18 +343,23 @@ describe('compiler: element transform', () => {
test('should handle <KeepAlive>', () => {
function assert(tag: string) {
const { root, node } = parseWithElementTransform(
`<${tag}><span /></${tag}>`
)
const root = parse(`<div><${tag}><span /></${tag}></div>`)
transform(root, {
nodeTransforms: [transformElement, transformText]
})
expect(root.components.length).toBe(0)
expect(root.helpers).toContain(KEEP_ALIVE)
expect(node.callee).toBe(CREATE_VNODE)
expect(node.arguments).toMatchObject([
KEEP_ALIVE,
`null`,
const node = (root.children[0] as any).children[0].codegenNode
expect(node).toMatchObject({
type: NodeTypes.VNODE_CALL,
tag: KEEP_ALIVE,
isBlock: true, // should be forced into a block
props: undefined,
// 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`)
@ -364,17 +373,17 @@ describe('compiler: element transform', () => {
)
expect(root.components.length).toBe(0)
expect(root.helpers).toContain(BASE_TRANSITION)
expect(node.callee).toBe(CREATE_VNODE)
expect(node.arguments).toMatchObject([
BASE_TRANSITION,
`null`,
createObjectMatcher({
expect(node).toMatchObject({
tag: BASE_TRANSITION,
props: undefined,
children: createObjectMatcher({
default: {
type: NodeTypes.JS_FUNCTION_EXPRESSION
},
_compiled: `[true]`
_: `[1]`
})
])
})
}
assert(`base-transition`)
@ -398,14 +407,13 @@ describe('compiler: element transform', () => {
foo(dir) {
_dir = dir
return {
props: [createObjectProperty(dir.arg!, dir.exp!)],
needRuntime: false
props: [createObjectProperty(dir.arg!, dir.exp!)]
}
}
}
})
expect(node.callee).toBe(CREATE_VNODE)
expect(node.arguments[1]).toMatchObject({
expect(node.props).toMatchObject({
type: NodeTypes.JS_OBJECT_EXPRESSION,
properties: [
{
@ -417,8 +425,8 @@ describe('compiler: element transform', () => {
})
// should factor in props returned by custom directive transforms
// in patchFlag analysis
expect(node.arguments[3]).toMatch(PatchFlags.PROPS + '')
expect(node.arguments[4]).toMatch(`"bar"`)
expect(node.patchFlag).toMatch(PatchFlags.PROPS + '')
expect(node.dynamicProps).toMatch(`"bar"`)
})
test('directiveTransform with needRuntime: true', () => {
@ -437,20 +445,12 @@ describe('compiler: element transform', () => {
)
expect(root.helpers).toContain(RESOLVE_DIRECTIVE)
expect(root.directives).toContain(`foo`)
expect(node.callee).toBe(WITH_DIRECTIVES)
expect(node.arguments).toMatchObject([
{
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_VNODE,
arguments: [
`"div"`,
`null`,
`null`,
genFlagText(PatchFlags.NEED_PATCH) // should generate appropriate flag
]
},
{
expect(node).toMatchObject({
tag: `"div"`,
props: undefined,
children: undefined,
patchFlag: genFlagText(PatchFlags.NEED_PATCH), // should generate appropriate flag
directives: {
type: NodeTypes.JS_ARRAY_EXPRESSION,
elements: [
{
@ -473,7 +473,7 @@ describe('compiler: element transform', () => {
}
]
}
])
})
})
test('directiveTransform with needRuntime: Symbol', () => {
@ -494,7 +494,7 @@ describe('compiler: element transform', () => {
expect(root.helpers).toContain(CREATE_VNODE)
expect(root.helpers).not.toContain(RESOLVE_DIRECTIVE)
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]}`
)
})
@ -508,12 +508,8 @@ describe('compiler: element transform', () => {
expect(root.directives).toContain(`bar`)
expect(root.directives).toContain(`baz`)
expect(node.callee).toBe(WITH_DIRECTIVES)
expect(node.arguments).toMatchObject([
{
type: NodeTypes.JS_CALL_EXPRESSION
},
{
expect(node).toMatchObject({
directives: {
type: NodeTypes.JS_ARRAY_EXPRESSION,
elements: [
{
@ -583,7 +579,7 @@ describe('compiler: element transform', () => {
}
]
}
])
})
})
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,
properties: [
{
@ -627,7 +623,7 @@ describe('compiler: element transform', () => {
test(`props merging: style`, () => {
const { node } = parseWithElementTransform(
`<div style="color: red" :style="{ color: 'red' }" />`,
`<div style="color: green" :style="{ color: 'red' }" />`,
{
nodeTransforms: [transformStyle, transformElement],
directiveTransforms: {
@ -635,7 +631,7 @@ describe('compiler: element transform', () => {
}
}
)
expect(node.arguments[1]).toMatchObject({
expect(node.props).toMatchObject({
type: NodeTypes.JS_OBJECT_EXPRESSION,
properties: [
{
@ -650,7 +646,7 @@ describe('compiler: element transform', () => {
elements: [
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: `_hoisted_1`,
content: `{"color":"green"}`,
isStatic: false
},
{
@ -674,7 +670,7 @@ describe('compiler: element transform', () => {
}
}
)
expect(node.arguments[1]).toMatchObject({
expect(node.props).toMatchObject({
type: NodeTypes.JS_OBJECT_EXPRESSION,
properties: [
{
@ -707,113 +703,175 @@ describe('compiler: element transform', () => {
describe('patchFlag analysis', () => {
test('TEXT', () => {
const { node } = parseWithBind(`<div>foo</div>`)
expect(node.arguments.length).toBe(3)
expect(node.patchFlag).toBeUndefined()
const { node: node2 } = parseWithBind(`<div>{{ foo }}</div>`)
expect(node2.arguments.length).toBe(4)
expect(node2.arguments[3]).toBe(genFlagText(PatchFlags.TEXT))
expect(node2.patchFlag).toBe(genFlagText(PatchFlags.TEXT))
// multiple nodes, merged with optimize text
const { node: node3 } = parseWithBind(`<div>foo {{ bar }} baz</div>`)
expect(node3.arguments.length).toBe(4)
expect(node3.arguments[3]).toBe(genFlagText(PatchFlags.TEXT))
expect(node3.patchFlag).toBe(genFlagText(PatchFlags.TEXT))
})
test('CLASS', () => {
const { node } = parseWithBind(`<div :class="foo" />`)
expect(node.arguments.length).toBe(4)
expect(node.arguments[3]).toBe(genFlagText(PatchFlags.CLASS))
expect(node.patchFlag).toBe(genFlagText(PatchFlags.CLASS))
})
test('STYLE', () => {
const { node } = parseWithBind(`<div :style="foo" />`)
expect(node.arguments.length).toBe(4)
expect(node.arguments[3]).toBe(genFlagText(PatchFlags.STYLE))
expect(node.patchFlag).toBe(genFlagText(PatchFlags.STYLE))
})
test('PROPS', () => {
const { node } = parseWithBind(`<div id="foo" :foo="bar" :baz="qux" />`)
expect(node.arguments.length).toBe(5)
expect(node.arguments[3]).toBe(genFlagText(PatchFlags.PROPS))
expect(node.arguments[4]).toBe(`["foo", "baz"]`)
expect(node.patchFlag).toBe(genFlagText(PatchFlags.PROPS))
expect(node.dynamicProps).toBe(`["foo", "baz"]`)
})
test('CLASS + STYLE + PROPS', () => {
const { node } = parseWithBind(
`<div id="foo" :class="cls" :style="styl" :foo="bar" :baz="qux"/>`
)
expect(node.arguments.length).toBe(5)
expect(node.arguments[3]).toBe(
expect(node.patchFlag).toBe(
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)', () => {
const { node } = parseWithBind(`<div v-bind="foo" />`)
expect(node.arguments.length).toBe(4)
expect(node.arguments[3]).toBe(genFlagText(PatchFlags.FULL_PROPS))
expect(node.patchFlag).toBe(genFlagText(PatchFlags.FULL_PROPS))
})
test('FULL_PROPS (dynamic key)', () => {
const { node } = parseWithBind(`<div :[foo]="bar" />`)
expect(node.arguments.length).toBe(4)
expect(node.arguments[3]).toBe(genFlagText(PatchFlags.FULL_PROPS))
expect(node.patchFlag).toBe(genFlagText(PatchFlags.FULL_PROPS))
})
test('FULL_PROPS (w/ others)', () => {
const { node } = parseWithBind(
`<div id="foo" v-bind="bar" :class="cls" />`
)
expect(node.arguments.length).toBe(4)
expect(node.arguments[3]).toBe(genFlagText(PatchFlags.FULL_PROPS))
expect(node.patchFlag).toBe(genFlagText(PatchFlags.FULL_PROPS))
})
test('NEED_PATCH (static ref)', () => {
const { node } = parseWithBind(`<div ref="foo" />`)
expect(node.arguments.length).toBe(4)
expect(node.arguments[3]).toBe(genFlagText(PatchFlags.NEED_PATCH))
expect(node.patchFlag).toBe(genFlagText(PatchFlags.NEED_PATCH))
})
test('NEED_PATCH (dynamic ref)', () => {
const { node } = parseWithBind(`<div :ref="foo" />`)
expect(node.arguments.length).toBe(4)
expect(node.arguments[3]).toBe(genFlagText(PatchFlags.NEED_PATCH))
expect(node.patchFlag).toBe(genFlagText(PatchFlags.NEED_PATCH))
})
test('NEED_PATCH (custom directives)', () => {
const { node } = parseWithBind(`<div v-foo />`)
const vnodeCall = node.arguments[0] as CallExpression
expect(vnodeCall.arguments.length).toBe(4)
expect(vnodeCall.arguments[3]).toBe(genFlagText(PatchFlags.NEED_PATCH))
expect(node.patchFlag).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', () => {
test('static binding', () => {
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({
callee: CREATE_VNODE,
arguments: ['_component_foo']
tag: {
callee: RESOLVE_DYNAMIC_COMPONENT,
arguments: [
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'foo',
isStatic: true
}
]
}
})
})
test('dynamic binding', () => {
const { node, root } = parseWithBind(`<component :is="foo" />`)
expect(root.helpers).toContain(RESOLVE_DYNAMIC_COMPONENT)
expect(node.arguments).toMatchObject([
{
expect(node).toMatchObject({
tag: {
callee: RESOLVE_DYNAMIC_COMPONENT,
arguments: [
{
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
})
})
})

View File

@ -1,5 +1,5 @@
import {
parse,
baseParse as parse,
transform,
ElementNode,
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', () => {
const node = parseWithExpressionTransform(
`{{ { [foo]: bar } }}`
@ -380,7 +390,65 @@ describe('compiler: expression transform', () => {
const onError = jest.fn()
parseWithExpressionTransform(`{{ a( }}`, { onError })
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` }]
})
})
})
})

View File

@ -1,6 +1,6 @@
import {
CompilerOptions,
parse,
baseParse as parse,
transform,
ElementNode,
NodeTypes,

View File

@ -1,6 +1,6 @@
import {
CompilerOptions,
parse,
baseParse as parse,
transform,
NodeTypes,
generate,

View File

@ -1,11 +1,11 @@
import {
parse,
baseParse as parse,
transform,
ElementNode,
ObjectExpression,
CompilerOptions,
ErrorCodes,
CallExpression
VNodeCall
} from '../../src'
import { transformBind } from '../../src/transforms/vBind'
import { transformElement } from '../../src/transforms/transformElement'
@ -33,8 +33,7 @@ function parseWithVBind(
describe('compiler: transform v-bind', () => {
test('basic', () => {
const node = parseWithVBind(`<div v-bind:id="id"/>`)
const props = (node.codegenNode as CallExpression)
.arguments[1] as ObjectExpression
const props = (node.codegenNode as VNodeCall).props as ObjectExpression
expect(props.properties[0]).toMatchObject({
key: {
content: `id`,
@ -69,8 +68,7 @@ describe('compiler: transform v-bind', () => {
test('dynamic arg', () => {
const node = parseWithVBind(`<div v-bind:[id]="id"/>`)
const props = (node.codegenNode as CallExpression)
.arguments[1] as ObjectExpression
const props = (node.codegenNode as VNodeCall).props as ObjectExpression
expect(props.properties[0]).toMatchObject({
key: {
content: `id`,
@ -103,8 +101,7 @@ describe('compiler: transform v-bind', () => {
test('.camel modifier', () => {
const node = parseWithVBind(`<div v-bind:foo-bar.camel="id"/>`)
const props = (node.codegenNode as CallExpression)
.arguments[1] as ObjectExpression
const props = (node.codegenNode as VNodeCall).props as ObjectExpression
expect(props.properties[0]).toMatchObject({
key: {
content: `fooBar`,
@ -119,8 +116,7 @@ describe('compiler: transform v-bind', () => {
test('.camel modifier w/ dynamic arg', () => {
const node = parseWithVBind(`<div v-bind:[foo].camel="id"/>`)
const props = (node.codegenNode as CallExpression)
.arguments[1] as ObjectExpression
const props = (node.codegenNode as VNodeCall).props as ObjectExpression
expect(props.properties[0]).toMatchObject({
key: {
content: `_${helperNameMap[CAMELIZE]}(foo)`,
@ -137,12 +133,11 @@ describe('compiler: transform v-bind', () => {
const node = parseWithVBind(`<div v-bind:[foo(bar)].camel="id"/>`, {
prefixIdentifiers: true
})
const props = (node.codegenNode as CallExpression)
.arguments[1] as ObjectExpression
const props = (node.codegenNode as VNodeCall).props as ObjectExpression
expect(props.properties[0]).toMatchObject({
key: {
children: [
`${helperNameMap[CAMELIZE]}(`,
`_${helperNameMap[CAMELIZE]}(`,
{ content: `_ctx.foo` },
`(`,
{ content: `_ctx.bar` },

View File

@ -1,4 +1,4 @@
import { parse } from '../../src/parse'
import { baseParse as parse } from '../../src/parse'
import { transform } from '../../src/transform'
import { transformIf } from '../../src/transforms/vIf'
import { transformFor } from '../../src/transforms/vFor'
@ -12,19 +12,11 @@ import {
SimpleExpressionNode,
ElementNode,
InterpolationNode,
CallExpression,
SequenceExpression
ForCodegenNode
} from '../../src/ast'
import { ErrorCodes } from '../../src/errors'
import { CompilerOptions, generate } from '../../src'
import {
OPEN_BLOCK,
CREATE_BLOCK,
FRAGMENT,
RENDER_LIST,
RENDER_SLOT,
WITH_DIRECTIVES
} from '../../src/runtimeHelpers'
import { FRAGMENT, RENDER_LIST, RENDER_SLOT } from '../../src/runtimeHelpers'
import { PatchFlags } from '@vue/shared'
import { createObjectMatcher, genFlagText } from '../testUtils'
@ -48,7 +40,7 @@ function parseWithForTransform(
})
return {
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', () => {
function assertSharedCodegen(
node: SequenceExpression,
node: ForCodegenNode,
keyed: boolean = false,
customReturn: boolean = false
) {
expect(node).toMatchObject({
type: NodeTypes.JS_SEQUENCE_EXPRESSION,
expressions: [
{
type: NodeTypes.JS_CALL_EXPRESSION,
callee: OPEN_BLOCK
},
{
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_BLOCK,
arguments: [
FRAGMENT,
`null`,
{
type: NodeTypes.JS_CALL_EXPRESSION,
callee: RENDER_LIST,
arguments: [
{}, // to be asserted by each test
{
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
}
]
}
type: NodeTypes.VNODE_CALL,
tag: FRAGMENT,
isForBlock: true,
patchFlag: keyed
? genFlagText(PatchFlags.KEYED_FRAGMENT)
: genFlagText(PatchFlags.UNKEYED_FRAGMENT),
children: {
type: NodeTypes.JS_CALL_EXPRESSION,
callee: RENDER_LIST,
arguments: [
{}, // to be asserted by each test
{
type: NodeTypes.JS_FUNCTION_EXPRESSION,
returns: customReturn
? {}
: {
type: NodeTypes.VNODE_CALL,
isBlock: true
}
]
},
keyed
? genFlagText(PatchFlags.KEYED_FRAGMENT)
: genFlagText(PatchFlags.UNKEYED_FRAGMENT)
]
}
]
}
]
}
})
const renderListArgs = ((node.expressions[1] as CallExpression)
.arguments[2] as CallExpression).arguments
const renderListArgs = node.children.arguments
return {
source: renderListArgs[0] as SimpleExpressionNode,
params: (renderListArgs[1] as any).params,
returns: (renderListArgs[1] as any).returns,
blockArgs: customReturn
? null
: (renderListArgs[1] as any).returns.expressions[1].arguments
innerVNodeCall: customReturn ? null : (renderListArgs[1] as any).returns
}
}
@ -635,7 +603,9 @@ describe('compiler: v-for', () => {
expect(assertSharedCodegen(codegenNode)).toMatchObject({
source: { content: `items` },
params: [{ content: `item` }],
blockArgs: [`"span"`]
innerVNodeCall: {
tag: `"span"`
}
})
expect(generate(root).code).toMatchSnapshot()
})
@ -698,15 +668,16 @@ describe('compiler: v-for', () => {
expect(assertSharedCodegen(codegenNode)).toMatchObject({
source: { content: `items` },
params: [{ content: `item` }],
blockArgs: [
FRAGMENT,
`null`,
[
innerVNodeCall: {
tag: FRAGMENT,
props: undefined,
isBlock: true,
children: [
{ type: NodeTypes.TEXT, content: `hello` },
{ type: NodeTypes.ELEMENT, tag: `span` }
],
genFlagText(PatchFlags.STABLE_FRAGMENT)
]
patchFlag: genFlagText(PatchFlags.STABLE_FRAGMENT)
}
})
expect(generate(root).code).toMatchSnapshot()
})
@ -757,12 +728,12 @@ describe('compiler: v-for', () => {
expect(assertSharedCodegen(codegenNode, true)).toMatchObject({
source: { content: `items` },
params: [{ content: `item` }],
blockArgs: [
`"span"`,
createObjectMatcher({
innerVNodeCall: {
tag: `"span"`,
props: createObjectMatcher({
key: `[item]`
})
]
}
})
expect(generate(root).code).toMatchSnapshot()
})
@ -777,17 +748,17 @@ describe('compiler: v-for', () => {
expect(assertSharedCodegen(codegenNode, true)).toMatchObject({
source: { content: `items` },
params: [{ content: `item` }],
blockArgs: [
FRAGMENT,
createObjectMatcher({
innerVNodeCall: {
tag: FRAGMENT,
props: createObjectMatcher({
key: `[item]`
}),
[
children: [
{ type: NodeTypes.TEXT, content: `hello` },
{ type: NodeTypes.ELEMENT, tag: `span` }
],
genFlagText(PatchFlags.STABLE_FRAGMENT)
]
patchFlag: genFlagText(PatchFlags.STABLE_FRAGMENT)
}
})
expect(generate(root).code).toMatchSnapshot()
})
@ -798,53 +769,33 @@ describe('compiler: v-for', () => {
node: { codegenNode }
} = parseWithForTransform(`<div v-if="ok" v-for="i in list"/>`)
expect(codegenNode).toMatchObject({
type: NodeTypes.JS_SEQUENCE_EXPRESSION,
expressions: [
{
type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
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,
callee: OPEN_BLOCK,
arguments: []
},
{
type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
test: { content: `ok` },
consequent: {
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_BLOCK,
// should optimize v-if + v-for into a single Fragment block
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)
]
}
callee: RENDER_LIST,
arguments: [
{ content: `list` },
{
type: NodeTypes.JS_FUNCTION_EXPRESSION,
params: [{ content: `i` }],
returns: {
type: NodeTypes.VNODE_CALL,
tag: `"div"`,
isBlock: true
}
}
]
}
]
}
})
expect(generate(root).code).toMatchSnapshot()
})
@ -856,18 +807,8 @@ describe('compiler: v-for', () => {
} = parseWithForTransform('<div v-for="i in list" v-foo/>')
const { returns } = assertSharedCodegen(codegenNode, false, true)
expect(returns).toMatchObject({
type: NodeTypes.JS_SEQUENCE_EXPRESSION,
expressions: [
{ callee: OPEN_BLOCK },
// should wrap withDirectives() around createBlock()
{
callee: WITH_DIRECTIVES,
arguments: [
{ callee: CREATE_BLOCK },
{ type: NodeTypes.JS_ARRAY_EXPRESSION }
]
}
]
type: NodeTypes.VNODE_CALL,
directives: { type: NodeTypes.JS_ARRAY_EXPRESSION }
})
expect(generate(root).code).toMatchSnapshot()
})

View File

@ -1,4 +1,4 @@
import { parse } from '../../src/parse'
import { baseParse as parse } from '../../src/parse'
import { transform } from '../../src/transform'
import { transformIf } from '../../src/transforms/vIf'
import { transformElement } from '../../src/transforms/transformElement'
@ -10,18 +10,16 @@ import {
TextNode,
CommentNode,
SimpleExpressionNode,
SequenceExpression,
ConditionalExpression,
CallExpression
IfConditionalExpression,
VNodeCall,
ElementTypes
} from '../../src/ast'
import { ErrorCodes } from '../../src/errors'
import { CompilerOptions, generate } from '../../src'
import {
OPEN_BLOCK,
CREATE_BLOCK,
FRAGMENT,
MERGE_PROPS,
WITH_DIRECTIVES,
RENDER_SLOT,
CREATE_COMMENT
} from '../../src/runtimeHelpers'
@ -43,7 +41,9 @@ function parseWithIfTransform(
}
return {
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`)
})
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', () => {
const { node } = parseWithIfTransform(`<div v-if="ok"/><p v-else/>`)
expect(node.type).toBe(NodeTypes.IF)
@ -266,49 +282,49 @@ describe('compiler: v-if', () => {
describe('codegen', () => {
function assertSharedCodegen(
node: SequenceExpression,
node: IfConditionalExpression,
depth: number = 0,
hasElse: boolean = false
) {
expect(node).toMatchObject({
type: NodeTypes.JS_SEQUENCE_EXPRESSION,
expressions: [
{
type: NodeTypes.JS_CALL_EXPRESSION,
callee: OPEN_BLOCK,
arguments: []
},
{
type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
test: {
content: `ok`
},
consequent: {
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_BLOCK
},
alternate:
depth < 1
? {
type: NodeTypes.JS_CALL_EXPRESSION,
callee: hasElse ? CREATE_BLOCK : CREATE_COMMENT
}
: {
type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
test: {
content: `orNot`
},
consequent: {
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_BLOCK
},
alternate: {
type: NodeTypes.JS_CALL_EXPRESSION,
callee: hasElse ? CREATE_BLOCK : CREATE_COMMENT
type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
test: {
content: `ok`
},
consequent: {
type: NodeTypes.VNODE_CALL,
isBlock: true
},
alternate:
depth < 1
? hasElse
? {
type: NodeTypes.VNODE_CALL,
isBlock: true
}
: {
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_COMMENT
}
: {
type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
test: {
content: `orNot`
},
consequent: {
type: NodeTypes.VNODE_CALL,
isBlock: true
},
alternate: hasElse
? {
type: NodeTypes.VNODE_CALL,
isBlock: true
}
}
}
]
: {
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_COMMENT
}
}
})
}
@ -318,15 +334,11 @@ describe('compiler: v-if', () => {
node: { codegenNode }
} = parseWithIfTransform(`<div v-if="ok"/>`)
assertSharedCodegen(codegenNode)
const branch1 = (codegenNode.expressions[1] as ConditionalExpression)
.consequent as CallExpression
expect(branch1.arguments).toMatchObject([
`"div"`,
createObjectMatcher({ key: `[0]` })
])
const branch2 = (codegenNode.expressions[1] as ConditionalExpression)
.alternate as CallExpression
expect(branch2).toMatchObject({
expect(codegenNode.consequent).toMatchObject({
tag: `"div"`,
props: createObjectMatcher({ key: `[0]` })
})
expect(codegenNode.alternate).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_COMMENT
})
@ -339,20 +351,16 @@ describe('compiler: v-if', () => {
node: { codegenNode }
} = parseWithIfTransform(`<template v-if="ok"><div/>hello<p/></template>`)
assertSharedCodegen(codegenNode)
const branch1 = (codegenNode.expressions[1] as ConditionalExpression)
.consequent as CallExpression
expect(branch1.arguments).toMatchObject([
FRAGMENT,
createObjectMatcher({ key: `[0]` }),
[
expect(codegenNode.consequent).toMatchObject({
tag: FRAGMENT,
props: createObjectMatcher({ key: `[0]` }),
children: [
{ type: NodeTypes.ELEMENT, tag: 'div' },
{ type: NodeTypes.TEXT, content: `hello` },
{ type: NodeTypes.ELEMENT, tag: 'p' }
]
])
const branch2 = (codegenNode.expressions[1] as ConditionalExpression)
.alternate as CallExpression
expect(branch2).toMatchObject({
})
expect(codegenNode.alternate).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_COMMENT
})
@ -364,10 +372,7 @@ describe('compiler: v-if', () => {
root,
node: { codegenNode }
} = parseWithIfTransform(`<template v-if="ok"><slot/></template>`)
// assertSharedCodegen(codegenNode)
const branch1 = (codegenNode.expressions[1] as ConditionalExpression)
.consequent as CallExpression
expect(branch1).toMatchObject({
expect(codegenNode.consequent).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: RENDER_SLOT,
arguments: ['$slots', '"default"', createObjectMatcher({ key: `[0]` })]
@ -380,10 +385,7 @@ describe('compiler: v-if', () => {
root,
node: { codegenNode }
} = parseWithIfTransform(`<slot v-if="ok"></slot>`)
// assertSharedCodegen(codegenNode)
const branch1 = (codegenNode.expressions[1] as ConditionalExpression)
.consequent as CallExpression
expect(branch1).toMatchObject({
expect(codegenNode.consequent).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: RENDER_SLOT,
arguments: ['$slots', '"default"', createObjectMatcher({ key: `[0]` })]
@ -397,18 +399,14 @@ describe('compiler: v-if', () => {
node: { codegenNode }
} = parseWithIfTransform(`<div v-if="ok"/><p v-else/>`)
assertSharedCodegen(codegenNode, 0, true)
const branch1 = (codegenNode.expressions[1] as ConditionalExpression)
.consequent as CallExpression
expect(branch1.arguments).toMatchObject([
`"div"`,
createObjectMatcher({ key: `[0]` })
])
const branch2 = (codegenNode.expressions[1] as ConditionalExpression)
.alternate as CallExpression
expect(branch2.arguments).toMatchObject([
`"p"`,
createObjectMatcher({ key: `[1]` })
])
expect(codegenNode.consequent).toMatchObject({
tag: `"div"`,
props: createObjectMatcher({ key: `[0]` })
})
expect(codegenNode.alternate).toMatchObject({
tag: `"p"`,
props: createObjectMatcher({ key: `[1]` })
})
expect(generate(root).code).toMatchSnapshot()
})
@ -418,18 +416,15 @@ describe('compiler: v-if', () => {
node: { codegenNode }
} = parseWithIfTransform(`<div v-if="ok"/><p v-else-if="orNot" />`)
assertSharedCodegen(codegenNode, 1)
const branch1 = (codegenNode.expressions[1] as ConditionalExpression)
.consequent as CallExpression
expect(branch1.arguments).toMatchObject([
`"div"`,
createObjectMatcher({ key: `[0]` })
])
const branch2 = (codegenNode.expressions[1] as ConditionalExpression)
.alternate as ConditionalExpression
expect((branch2.consequent as CallExpression).arguments).toMatchObject([
`"p"`,
createObjectMatcher({ key: `[1]` })
])
expect(codegenNode.consequent).toMatchObject({
tag: `"div"`,
props: createObjectMatcher({ key: `[0]` })
})
const branch2 = codegenNode.alternate as ConditionalExpression
expect(branch2.consequent).toMatchObject({
tag: `"p"`,
props: createObjectMatcher({ key: `[1]` })
})
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>`
)
assertSharedCodegen(codegenNode, 1, true)
const branch1 = (codegenNode.expressions[1] as ConditionalExpression)
.consequent as CallExpression
expect(branch1.arguments).toMatchObject([
`"div"`,
createObjectMatcher({ key: `[0]` })
])
const branch2 = (codegenNode.expressions[1] as ConditionalExpression)
.alternate as ConditionalExpression
expect((branch2.consequent as CallExpression).arguments).toMatchObject([
`"p"`,
createObjectMatcher({ key: `[1]` })
])
expect((branch2.alternate as CallExpression).arguments).toMatchObject([
FRAGMENT,
createObjectMatcher({ key: `[2]` }),
[
expect(codegenNode.consequent).toMatchObject({
tag: `"div"`,
props: createObjectMatcher({ key: `[0]` })
})
const branch2 = codegenNode.alternate as ConditionalExpression
expect(branch2.consequent).toMatchObject({
tag: `"p"`,
props: createObjectMatcher({ key: `[1]` })
})
expect(branch2.alternate).toMatchObject({
tag: FRAGMENT,
props: createObjectMatcher({ key: `[2]` }),
children: [
{
type: NodeTypes.TEXT,
content: `fine`
}
]
])
})
expect(generate(root).code).toMatchSnapshot()
})
@ -470,9 +462,8 @@ describe('compiler: v-if', () => {
const {
node: { codegenNode }
} = parseWithIfTransform(`<div v-if="ok" v-bind="obj"/>`)
const branch1 = (codegenNode.expressions[1] as ConditionalExpression)
.consequent as CallExpression
expect(branch1.arguments[1]).toMatchObject({
const branch1 = codegenNode.consequent as VNodeCall
expect(branch1.props).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: MERGE_PROPS,
arguments: [createObjectMatcher({ key: `[0]` }), { content: `obj` }]
@ -483,9 +474,8 @@ describe('compiler: v-if', () => {
const {
node: { codegenNode }
} = parseWithIfTransform(`<div v-if="ok" id="foo" v-bind="obj"/>`)
const branch1 = (codegenNode.expressions[1] as ConditionalExpression)
.consequent as CallExpression
expect(branch1.arguments[1]).toMatchObject({
const branch1 = codegenNode.consequent as VNodeCall
expect(branch1.props).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: MERGE_PROPS,
arguments: [
@ -502,9 +492,8 @@ describe('compiler: v-if', () => {
const {
node: { codegenNode }
} = parseWithIfTransform(`<div v-if="ok" v-bind="obj" id="foo"/>`)
const branch1 = (codegenNode.expressions[1] as ConditionalExpression)
.consequent as CallExpression
expect(branch1.arguments[1]).toMatchObject({
const branch1 = codegenNode.consequent as VNodeCall
expect(branch1.props).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: MERGE_PROPS,
arguments: [
@ -521,13 +510,21 @@ describe('compiler: v-if', () => {
const {
node: { codegenNode }
} = parseWithIfTransform(`<div v-if="ok" v-foo />`)
const branch1 = (codegenNode.expressions[1] as ConditionalExpression)
.consequent as CallExpression
expect(branch1.callee).toBe(WITH_DIRECTIVES)
const realBranch = branch1.arguments[0] as CallExpression
expect(realBranch.arguments[1]).toMatchObject(
createObjectMatcher({ key: `[0]` })
)
const branch1 = codegenNode.consequent as VNodeCall
expect(branch1.directives).not.toBeUndefined()
expect(branch1.props).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')

View File

@ -1,16 +1,15 @@
import {
parse,
baseParse as parse,
transform,
generate,
ElementNode,
ObjectExpression,
CompilerOptions,
CallExpression,
ForNode,
PlainElementNode,
PlainElementCodegenNode,
ComponentNode,
NodeTypes
NodeTypes,
VNodeCall
} from '../../src'
import { ErrorCodes } from '../../src/errors'
import { transformModel } from '../../src/transforms/vModel'
@ -43,8 +42,8 @@ describe('compiler: transform v-model', () => {
test('simple exprssion', () => {
const root = parseWithVModel('<input v-model="model" />')
const node = root.children[0] as ElementNode
const props = ((node.codegenNode as CallExpression)
.arguments[1] as ObjectExpression).properties
const props = ((node.codegenNode as VNodeCall).props as ObjectExpression)
.properties
expect(props[0]).toMatchObject({
key: {
@ -82,8 +81,8 @@ describe('compiler: transform v-model', () => {
prefixIdentifiers: true
})
const node = root.children[0] as ElementNode
const props = ((node.codegenNode as CallExpression)
.arguments[1] as ObjectExpression).properties
const props = ((node.codegenNode as VNodeCall).props as ObjectExpression)
.properties
expect(props[0]).toMatchObject({
key: {
@ -119,8 +118,8 @@ describe('compiler: transform v-model', () => {
test('compound expression', () => {
const root = parseWithVModel('<input v-model="model[index]" />')
const node = root.children[0] as ElementNode
const props = ((node.codegenNode as CallExpression)
.arguments[1] as ObjectExpression).properties
const props = ((node.codegenNode as VNodeCall).props as ObjectExpression)
.properties
expect(props[0]).toMatchObject({
key: {
@ -158,8 +157,8 @@ describe('compiler: transform v-model', () => {
prefixIdentifiers: true
})
const node = root.children[0] as ElementNode
const props = ((node.codegenNode as CallExpression)
.arguments[1] as ObjectExpression).properties
const props = ((node.codegenNode as VNodeCall).props as ObjectExpression)
.properties
expect(props[0]).toMatchObject({
key: {
@ -191,15 +190,19 @@ describe('compiler: transform v-model', () => {
children: [
'$event => (',
{
content: '_ctx.model',
isStatic: false
children: [
{
content: '_ctx.model',
isStatic: false
},
'[',
{
content: '_ctx.index',
isStatic: false
},
']'
]
},
'[',
{
content: '_ctx.index',
isStatic: false
},
']',
' = $event)'
]
}
@ -211,9 +214,8 @@ describe('compiler: transform v-model', () => {
test('with argument', () => {
const root = parseWithVModel('<input v-model:value="model" />')
const node = root.children[0] as ElementNode
const props = ((node.codegenNode as CallExpression)
.arguments[1] as ObjectExpression).properties
const props = ((node.codegenNode as VNodeCall).props as ObjectExpression)
.properties
expect(props[0]).toMatchObject({
key: {
content: 'value',
@ -248,8 +250,8 @@ describe('compiler: transform v-model', () => {
test('with dynamic argument', () => {
const root = parseWithVModel('<input v-model:[value]="model" />')
const node = root.children[0] as ElementNode
const props = ((node.codegenNode as CallExpression)
.arguments[1] as ObjectExpression).properties
const props = ((node.codegenNode as VNodeCall).props as ObjectExpression)
.properties
expect(props[0]).toMatchObject({
key: {
@ -292,8 +294,8 @@ describe('compiler: transform v-model', () => {
prefixIdentifiers: true
})
const node = root.children[0] as ElementNode
const props = ((node.codegenNode as CallExpression)
.arguments[1] as ObjectExpression).properties
const props = ((node.codegenNode as VNodeCall).props as ObjectExpression)
.properties
expect(props[0]).toMatchObject({
key: {
@ -338,12 +340,12 @@ describe('compiler: transform v-model', () => {
})
expect(root.cached).toBe(1)
const codegen = (root.children[0] as PlainElementNode)
.codegenNode as PlainElementCodegenNode
.codegenNode as VNodeCall
// should not list cached prop in dynamicProps
expect(codegen.arguments[4]).toBe(`["modelValue"]`)
expect(
(codegen.arguments[1] as ObjectExpression).properties[1].value.type
).toBe(NodeTypes.JS_CACHE_EXPRESSION)
expect(codegen.dynamicProps).toBe(`["modelValue"]`)
expect((codegen.props as ObjectExpression).properties[1].value.type).toBe(
NodeTypes.JS_CACHE_EXPRESSION
)
})
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)
const codegen = ((root.children[0] as ForNode)
.children[0] as PlainElementNode).codegenNode as PlainElementCodegenNode
expect(codegen.arguments[4]).toBe(`["modelValue", "onUpdate:modelValue"]`)
.children[0] as PlainElementNode).codegenNode as VNodeCall
expect(codegen.dynamicProps).toBe(`["modelValue", "onUpdate:modelValue"]`)
expect(
(codegen.arguments[1] as ObjectExpression).properties[1].value.type
(codegen.props as ObjectExpression).properties[1].value.type
).not.toBe(NodeTypes.JS_CACHE_EXPRESSION)
})
@ -371,18 +373,18 @@ describe('compiler: transform v-model', () => {
}
)
const codegen = ((root.children[0] as ComponentNode)
.children[0] as PlainElementNode).codegenNode as PlainElementCodegenNode
expect(codegen.arguments[4]).toBe(`["modelValue", "onUpdate:modelValue"]`)
.children[0] as PlainElementNode).codegenNode as VNodeCall
expect(codegen.dynamicProps).toBe(`["modelValue", "onUpdate:modelValue"]`)
})
test('should generate modelModifers for component v-model', () => {
const root = parseWithVModel('<Comp v-model.trim.bar-baz="foo" />', {
prefixIdentifiers: true
})
const args = ((root.children[0] as ComponentNode)
.codegenNode as CallExpression).arguments
const vnodeCall = (root.children[0] as ComponentNode)
.codegenNode as VNodeCall
// props
expect(args[1]).toMatchObject({
expect(vnodeCall.props).toMatchObject({
properties: [
{ key: { content: `modelValue` } },
{ key: { content: `onUpdate:modelValue` } },
@ -394,7 +396,7 @@ describe('compiler: transform v-model', () => {
})
// should NOT include modelModifiers in dynamicPropNames because it's never
// 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', () => {
@ -404,10 +406,10 @@ describe('compiler: transform v-model', () => {
prefixIdentifiers: true
}
)
const args = ((root.children[0] as ComponentNode)
.codegenNode as CallExpression).arguments
const vnodeCall = (root.children[0] as ComponentNode)
.codegenNode as VNodeCall
// props
expect(args[1]).toMatchObject({
expect(vnodeCall.props).toMatchObject({
properties: [
{ key: { content: `foo` } },
{ key: { content: `onUpdate:foo` } },
@ -425,7 +427,9 @@ describe('compiler: transform v-model', () => {
})
// should NOT include modelModifiers in dynamicPropNames because it's never
// gonna change
expect(args[4]).toBe(`["foo", "onUpdate:foo", "bar", "onUpdate:bar"]`)
expect(vnodeCall.dynamicProps).toBe(
`["foo", "onUpdate:foo", "bar", "onUpdate:bar"]`
)
})
describe('errors', () => {

View File

@ -1,13 +1,12 @@
import {
parse,
baseParse as parse,
transform,
ElementNode,
ObjectExpression,
CompilerOptions,
ErrorCodes,
NodeTypes,
CallExpression,
PlainElementCodegenNode
VNodeCall
} from '../../src'
import { transformOn } from '../../src/transforms/vOn'
import { transformElement } from '../../src/transforms/transformElement'
@ -31,54 +30,58 @@ function parseWithVOn(template: string, options: CompilerOptions = {}) {
describe('compiler: transform v-on', () => {
test('basic', () => {
const { node } = parseWithVOn(`<div v-on:click="onClick"/>`)
const props = (node.codegenNode as CallExpression)
.arguments[1] as ObjectExpression
expect(props.properties[0]).toMatchObject({
key: {
content: `onClick`,
isStatic: true,
loc: {
start: {
line: 1,
column: 11
expect((node.codegenNode as VNodeCall).props).toMatchObject({
properties: [
{
key: {
content: `onClick`,
isStatic: true,
loc: {
start: {
line: 1,
column: 11
},
end: {
line: 1,
column: 16
}
}
},
end: {
line: 1,
column: 16
value: {
content: `onClick`,
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', () => {
const { node } = parseWithVOn(`<div v-on:[event]="handler"/>`)
const props = (node.codegenNode as CallExpression)
.arguments[1] as ObjectExpression
expect(props.properties[0]).toMatchObject({
key: {
type: NodeTypes.COMPOUND_EXPRESSION,
children: [`"on" + (`, { content: `event` }, `)`]
},
value: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `handler`,
isStatic: false
}
expect((node.codegenNode as VNodeCall).props).toMatchObject({
properties: [
{
key: {
type: NodeTypes.COMPOUND_EXPRESSION,
children: [`"on" + (`, { content: `event` }, `)`]
},
value: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `handler`,
isStatic: false
}
}
]
})
})
@ -86,18 +89,20 @@ describe('compiler: transform v-on', () => {
const { node } = parseWithVOn(`<div v-on:[event]="handler"/>`, {
prefixIdentifiers: true
})
const props = (node.codegenNode as CallExpression)
.arguments[1] as ObjectExpression
expect(props.properties[0]).toMatchObject({
key: {
type: NodeTypes.COMPOUND_EXPRESSION,
children: [`"on" + (`, { content: `_ctx.event` }, `)`]
},
value: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `_ctx.handler`,
isStatic: false
}
expect((node.codegenNode as VNodeCall).props).toMatchObject({
properties: [
{
key: {
type: NodeTypes.COMPOUND_EXPRESSION,
children: [`"on" + (`, { content: `_ctx.event` }, `)`]
},
value: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `_ctx.handler`,
isStatic: false
}
}
]
})
})
@ -105,38 +110,60 @@ describe('compiler: transform v-on', () => {
const { node } = parseWithVOn(`<div v-on:[event(foo)]="handler"/>`, {
prefixIdentifiers: true
})
const props = (node.codegenNode as CallExpression)
.arguments[1] as ObjectExpression
expect(props.properties[0]).toMatchObject({
key: {
type: NodeTypes.COMPOUND_EXPRESSION,
children: [
`"on" + (`,
{ content: `_ctx.event` },
`(`,
{ content: `_ctx.foo` },
`)`,
`)`
]
},
value: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `_ctx.handler`,
isStatic: false
}
expect((node.codegenNode as VNodeCall).props).toMatchObject({
properties: [
{
key: {
type: NodeTypes.COMPOUND_EXPRESSION,
children: [
`"on" + (`,
{ content: `_ctx.event` },
`(`,
{ content: `_ctx.foo` },
`)`,
`)`
]
},
value: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `_ctx.handler`,
isStatic: false
}
}
]
})
})
test('should wrap as function if expression is inline statement', () => {
const { node } = parseWithVOn(`<div @click="i++"/>`)
const props = (node.codegenNode as CallExpression)
.arguments[1] as ObjectExpression
expect(props.properties[0]).toMatchObject({
key: { content: `onClick` },
value: {
type: NodeTypes.COMPOUND_EXPRESSION,
children: [`$event => (`, { content: `i++` }, `)`]
}
expect((node.codegenNode as VNodeCall).props).toMatchObject({
properties: [
{
key: { content: `onClick` },
value: {
type: NodeTypes.COMPOUND_EXPRESSION,
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)"/>`, {
prefixIdentifiers: true
})
const props = (node.codegenNode as CallExpression)
.arguments[1] as ObjectExpression
expect(props.properties[0]).toMatchObject({
key: { content: `onClick` },
value: {
type: NodeTypes.COMPOUND_EXPRESSION,
children: [
`$event => (`,
{ content: `_ctx.foo` },
`(`,
// should NOT prefix $event
{ content: `$event` },
`)`,
`)`
]
}
expect((node.codegenNode as VNodeCall).props).toMatchObject({
properties: [
{
key: { content: `onClick` },
value: {
type: NodeTypes.COMPOUND_EXPRESSION,
children: [
`$event => (`,
{
type: NodeTypes.COMPOUND_EXPRESSION,
children: [
{ 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', () => {
const { node } = parseWithVOn(`<div @click="$event => foo($event)"/>`)
const props = (node.codegenNode as CallExpression)
.arguments[1] as ObjectExpression
expect(props.properties[0]).toMatchObject({
key: { content: `onClick` },
value: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `$event => foo($event)`
}
expect((node.codegenNode as VNodeCall).props).toMatchObject({
properties: [
{
key: { content: `onClick` },
value: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `$event => foo($event)`
}
}
]
})
})
test('should NOT wrap as function if expression is complex member expression', () => {
const { node } = parseWithVOn(`<div @click="a['b' + c]"/>`)
const props = (node.codegenNode as CallExpression)
.arguments[1] as ObjectExpression
expect(props.properties[0]).toMatchObject({
key: { content: `onClick` },
value: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `a['b' + c]`
}
expect((node.codegenNode as VNodeCall).props).toMatchObject({
properties: [
{
key: { content: `onClick` },
value: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `a['b' + c]`
}
}
]
})
})
@ -193,14 +262,21 @@ describe('compiler: transform v-on', () => {
const { node } = parseWithVOn(`<div @click="a['b' + c]"/>`, {
prefixIdentifiers: true
})
const props = (node.codegenNode as CallExpression)
.arguments[1] as ObjectExpression
expect(props.properties[0]).toMatchObject({
key: { content: `onClick` },
value: {
type: NodeTypes.COMPOUND_EXPRESSION,
children: [{ content: `_ctx.a` }, `['b' + `, { content: `_ctx.c` }, `]`]
}
expect((node.codegenNode as VNodeCall).props).toMatchObject({
properties: [
{
key: { content: `onClick` },
value: {
type: NodeTypes.COMPOUND_EXPRESSION,
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)"/>`, {
prefixIdentifiers: true
})
const props = (node.codegenNode as CallExpression)
.arguments[1] as ObjectExpression
expect(props.properties[0]).toMatchObject({
key: { content: `onClick` },
value: {
type: NodeTypes.COMPOUND_EXPRESSION,
children: [
{ content: `e` },
` => `,
{ content: `_ctx.foo` },
`(`,
{ content: `e` },
`)`
]
}
expect((node.codegenNode as VNodeCall).props).toMatchObject({
properties: [
{
key: { content: `onClick` },
value: {
type: NodeTypes.COMPOUND_EXPRESSION,
children: [
{ content: `e` },
` => `,
{ content: `_ctx.foo` },
`(`,
{ content: `e` },
`)`
]
}
}
]
})
})
@ -250,6 +328,22 @@ describe('compiler: transform v-on', () => {
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', () => {
test('empty handler', () => {
const { root, node } = parseWithVOn(`<div v-on:click.prevent />`, {
@ -257,10 +351,12 @@ describe('compiler: transform v-on', () => {
cacheHandlers: true
})
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
expect(args.length).toBe(2)
expect((args[1] as ObjectExpression).properties[0].value).toMatchObject({
expect(vnodeCall.patchFlag).toBeUndefined()
expect(
(vnodeCall.props as ObjectExpression).properties[0].value
).toMatchObject({
type: NodeTypes.JS_CACHE_EXPRESSION,
index: 1,
value: {
@ -276,10 +372,12 @@ describe('compiler: transform v-on', () => {
cacheHandlers: true
})
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
expect(args.length).toBe(2)
expect((args[1] as ObjectExpression).properties[0].value).toMatchObject({
expect(vnodeCall.patchFlag).toBeUndefined()
expect(
(vnodeCall.props as ObjectExpression).properties[0].value
).toMatchObject({
type: NodeTypes.JS_CACHE_EXPRESSION,
index: 1,
value: {
@ -295,10 +393,12 @@ describe('compiler: transform v-on', () => {
cacheHandlers: true
})
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
expect(args.length).toBe(2)
expect((args[1] as ObjectExpression).properties[0].value).toMatchObject({
expect(vnodeCall.patchFlag).toBeUndefined()
expect(
(vnodeCall.props as ObjectExpression).properties[0].value
).toMatchObject({
type: NodeTypes.JS_CACHE_EXPRESSION,
index: 1,
value: {
@ -314,15 +414,22 @@ describe('compiler: transform v-on', () => {
cacheHandlers: true
})
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
expect(args.length).toBe(2)
expect((args[1] as ObjectExpression).properties[0].value).toMatchObject({
expect(vnodeCall.patchFlag).toBeUndefined()
expect(
(vnodeCall.props as ObjectExpression).properties[0].value
).toMatchObject({
type: NodeTypes.JS_CACHE_EXPRESSION,
index: 1,
value: {
type: NodeTypes.COMPOUND_EXPRESSION,
children: [`$event => (`, { content: `_ctx.foo` }, `++`, `)`]
children: [
`$event => (`,
{ children: [{ content: `_ctx.foo` }, `++`] },
`)`
]
}
})
})

View File

@ -1,5 +1,5 @@
import {
parse,
baseParse as parse,
transform,
NodeTypes,
generate,
@ -7,11 +7,7 @@ import {
} from '../../src'
import { transformOnce } from '../../src/transforms/vOnce'
import { transformElement } from '../../src/transforms/transformElement'
import {
CREATE_VNODE,
RENDER_SLOT,
SET_BLOCK_TRACKING
} from '../../src/runtimeHelpers'
import { RENDER_SLOT, SET_BLOCK_TRACKING } from '../../src/runtimeHelpers'
import { transformBind } from '../../src/transforms/vBind'
import { transformSlotOutlet } from '../../src/transforms/transformSlotOutlet'
@ -36,8 +32,8 @@ describe('compiler: v-once transform', () => {
type: NodeTypes.JS_CACHE_EXPRESSION,
index: 1,
value: {
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_VNODE
type: NodeTypes.VNODE_CALL,
tag: `"div"`
}
})
expect(generate(root).code).toMatchSnapshot()
@ -51,8 +47,8 @@ describe('compiler: v-once transform', () => {
type: NodeTypes.JS_CACHE_EXPRESSION,
index: 1,
value: {
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_VNODE
type: NodeTypes.VNODE_CALL,
tag: `"div"`
}
})
expect(generate(root).code).toMatchSnapshot()
@ -66,8 +62,8 @@ describe('compiler: v-once transform', () => {
type: NodeTypes.JS_CACHE_EXPRESSION,
index: 1,
value: {
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_VNODE
type: NodeTypes.VNODE_CALL,
tag: `_component_Comp`
}
})
expect(generate(root).code).toMatchSnapshot()
@ -100,8 +96,8 @@ describe('compiler: v-once transform', () => {
type: NodeTypes.JS_CACHE_EXPRESSION,
index: 1,
value: {
type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_VNODE
type: NodeTypes.VNODE_CALL,
tag: `"div"`
}
})
expect(generate(root).code).toMatchSnapshot()

View File

@ -1,14 +1,15 @@
import {
CompilerOptions,
parse,
baseParse as parse,
transform,
generate,
ElementNode,
NodeTypes,
ErrorCodes,
ForNode,
CallExpression,
ComponentNode
ComponentNode,
VNodeCall,
SlotsExpression
} from '../../src'
import { transformElement } from '../../src/transforms/transformElement'
import { transformOn } from '../../src/transforms/vOn'
@ -46,7 +47,8 @@ function parseWithSlots(template: string, options: CompilerOptions = {}) {
root: ast,
slots:
ast.children[0].type === NodeTypes.ELEMENT
? (ast.children[0].codegenNode as CallExpression).arguments[2]
? ((ast.children[0].codegenNode as VNodeCall)
.children as SlotsExpression)
: null
}
}
@ -67,8 +69,8 @@ function createSlotMatcher(obj: Record<string, any>) {
} as any
})
.concat({
key: { content: `_compiled` },
value: { content: `true` }
key: { content: `_` },
value: { content: `1`, isStatic: false }
})
}
}
@ -95,7 +97,7 @@ describe('compiler: transform component slots', () => {
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
})
test('explicit default slot', () => {
test('on-component default slot', () => {
const { root, slots } = parseWithSlots(
`<Comp v-slot="{ foo }">{{ foo }}{{ bar }}</Comp>`,
{ prefixIdentifiers: true }
@ -128,7 +130,40 @@ describe('compiler: transform component slots', () => {
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(
`<Comp>
<template v-slot:one="{ foo }">
@ -189,6 +224,76 @@ describe('compiler: transform component slots', () => {
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', () => {
const { root, slots } = parseWithSlots(
`<Comp>
@ -274,43 +379,41 @@ describe('compiler: transform component slots', () => {
{
type: NodeTypes.ELEMENT,
codegenNode: {
type: NodeTypes.JS_CALL_EXPRESSION,
arguments: [
`_component_Inner`,
`null`,
createSlotMatcher({
default: {
type: NodeTypes.JS_FUNCTION_EXPRESSION,
params: {
type: NodeTypes.COMPOUND_EXPRESSION,
children: [`{ `, { content: `bar` }, ` }`]
},
returns: [
{
type: NodeTypes.INTERPOLATION,
content: {
content: `foo`
}
},
{
type: NodeTypes.INTERPOLATION,
content: {
content: `bar`
}
},
{
type: NodeTypes.INTERPOLATION,
content: {
content: `_ctx.baz`
}
type: NodeTypes.VNODE_CALL,
tag: `_component_Inner`,
props: undefined,
children: createSlotMatcher({
default: {
type: NodeTypes.JS_FUNCTION_EXPRESSION,
params: {
type: NodeTypes.COMPOUND_EXPRESSION,
children: [`{ `, { content: `bar` }, ` }`]
},
returns: [
{
type: NodeTypes.INTERPOLATION,
content: {
content: `foo`
}
]
}
}),
// nested slot should be forced dynamic, since scope variables
// are not tracked as dependencies of the slot.
genFlagText(PatchFlags.DYNAMIC_SLOTS)
]
},
{
type: NodeTypes.INTERPOLATION,
content: {
content: `bar`
}
},
{
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
@ -351,8 +454,8 @@ describe('compiler: transform component slots', () => {
)
const div = ((root.children[0] as ForNode).children[0] as ElementNode)
.codegenNode as any
const comp = div.arguments[2][0]
expect(comp.codegenNode.arguments[3]).toBe(
const comp = div.children[0]
expect(comp.codegenNode.patchFlag).toBe(
genFlagText(PatchFlags.DYNAMIC_SLOTS)
)
})
@ -364,12 +467,12 @@ describe('compiler: transform component slots', () => {
if (root.children[0].type === NodeTypes.FOR) {
const div = (root.children[0].children[0] as ElementNode)
.codegenNode as any
const comp = div.arguments[2][0]
flag = comp.codegenNode.arguments[3]
const comp = div.children[0]
flag = comp.codegenNode.patchFlag
} else {
const innerComp = (root.children[0] as ComponentNode)
.children[0] as ComponentNode
flag = (innerComp.codegenNode as CallExpression).arguments[3]
flag = (innerComp.codegenNode as VNodeCall).patchFlag
}
if (shouldForce) {
expect(flag).toBe(genFlagText(PatchFlags.DYNAMIC_SLOTS))
@ -419,7 +522,7 @@ describe('compiler: transform component slots', () => {
callee: CREATE_SLOTS,
arguments: [
createObjectMatcher({
_compiled: `[true]`
_: `[1]`
}),
{
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 + ''
)
expect(generate(root).code).toMatchSnapshot()
@ -461,7 +564,7 @@ describe('compiler: transform component slots', () => {
callee: CREATE_SLOTS,
arguments: [
createObjectMatcher({
_compiled: `[true]`
_: `[1]`
}),
{
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 + ''
)
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
@ -510,7 +613,7 @@ describe('compiler: transform component slots', () => {
callee: CREATE_SLOTS,
arguments: [
createObjectMatcher({
_compiled: `[true]`
_: `[1]`
}),
{
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 + ''
)
expect(generate(root).code).toMatchSnapshot()
@ -569,7 +672,7 @@ describe('compiler: transform component slots', () => {
callee: CREATE_SLOTS,
arguments: [
createObjectMatcher({
_compiled: `[true]`
_: `[1]`
}),
{
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 + ''
)
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
})
describe('errors', () => {
test('error on extraneous children w/ named slots', () => {
test('error on extraneous children w/ named default slot', () => {
const onError = jest.fn()
const source = `<Comp><template #default>foo</template>bar</Comp>`
parseWithSlots(source, { onError })
const index = source.indexOf('bar')
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: {
source: `bar`,
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
}
}
})
})
})
})

View File

@ -1,15 +1,16 @@
{
"name": "@vue/compiler-core",
"version": "3.0.0-alpha.0",
"version": "3.0.0-alpha.11",
"description": "@vue/compiler-core",
"main": "index.js",
"module": "dist/compiler-core.esm-bundler.js",
"types": "dist/compiler-core.d.ts",
"files": [
"index.js",
"dist"
],
"types": "dist/compiler-core.d.ts",
"buildOptions": {
"name": "VueCompilerCore",
"formats": [
"esm-bundler",
"cjs"
@ -17,7 +18,7 @@
},
"repository": {
"type": "git",
"url": "git+https://github.com/vuejs/vue.git"
"url": "git+https://github.com/vuejs/vue-next.git"
},
"keywords": [
"vue"
@ -25,12 +26,14 @@
"author": "Evan You",
"license": "MIT",
"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": {
"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",
"source-map": "^0.7.3"
"source-map": "^0.6.1"
}
}

View File

@ -1,17 +1,17 @@
import { isString } from '@vue/shared'
import { ForParseResult } from './transforms/vFor'
import {
CREATE_VNODE,
WITH_DIRECTIVES,
RENDER_SLOT,
CREATE_SLOTS,
RENDER_LIST,
OPEN_BLOCK,
CREATE_BLOCK,
FRAGMENT
FRAGMENT,
CREATE_VNODE,
WITH_DIRECTIVES
} from './runtimeHelpers'
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).
// More namespaces like SVG and MathML are declared by platform specific
@ -38,14 +38,22 @@ export const enum NodeTypes {
FOR,
TEXT_CALL,
// codegen
VNODE_CALL,
JS_CALL_EXPRESSION,
JS_OBJECT_EXPRESSION,
JS_PROPERTY,
JS_ARRAY_EXPRESSION,
JS_FUNCTION_EXPRESSION,
JS_SEQUENCE_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 {
@ -85,6 +93,7 @@ export type TemplateChildNode =
| TextNode
| CommentNode
| IfNode
| IfBranchNode
| ForNode
| TextCallNode
@ -97,7 +106,9 @@ export interface RootNode extends Node {
hoists: JSChildNode[]
imports: ImportItem[]
cached: number
codegenNode: TemplateChildNode | JSChildNode | undefined
temps: number
ssrHelpers?: symbol[]
codegenNode?: TemplateChildNode | JSChildNode | BlockStatement | undefined
}
export type ElementNode =
@ -114,35 +125,40 @@ export interface BaseElementNode extends Node {
isSelfClosing: boolean
props: Array<AttributeNode | DirectiveNode>
children: TemplateChildNode[]
codegenNode:
| CallExpression
| SimpleExpressionNode
| CacheExpression
| undefined
}
export interface PlainElementNode extends BaseElementNode {
tagType: ElementTypes.ELEMENT
codegenNode:
| ElementCodegenNode
| undefined
| VNodeCall
| SimpleExpressionNode // when hoisted
| CacheExpression // when cached by v-once
| undefined
ssrCodegenNode?: TemplateLiteral
}
export interface ComponentNode extends BaseElementNode {
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 {
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 {
tagType: ElementTypes.TEMPLATE
codegenNode: ElementCodegenNode | undefined | CacheExpression
// TemplateNode is a container type that always gets compiled away
codegenNode: undefined
}
export interface TextNode extends Node {
@ -190,6 +206,7 @@ export interface CompoundExpressionNode extends Node {
type: NodeTypes.COMPOUND_EXPRESSION
children: (
| SimpleExpressionNode
| CompoundExpressionNode
| InterpolationNode
| TextNode
| string
@ -202,7 +219,7 @@ export interface CompoundExpressionNode extends Node {
export interface IfNode extends Node {
type: NodeTypes.IF
branches: IfBranchNode[]
codegenNode: IfCodegenNode
codegenNode?: IfConditionalExpression
}
export interface IfBranchNode extends Node {
@ -217,28 +234,56 @@ export interface ForNode extends Node {
valueAlias: ExpressionNode | undefined
keyAlias: ExpressionNode | undefined
objectIndexAlias: ExpressionNode | undefined
parseResult: ForParseResult
children: TemplateChildNode[]
codegenNode: ForCodegenNode
codegenNode?: ForCodegenNode
}
export interface TextCallNode extends Node {
type: NodeTypes.TEXT_CALL
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.
// The AST is an intentionally minimal subset just to meet the exact needs of
// Vue render function generation.
export type JSChildNode =
| VNodeCall
| CallExpression
| ObjectExpression
| ArrayExpression
| ExpressionNode
| FunctionExpression
| ConditionalExpression
| SequenceExpression
| CacheExpression
| AssignmentExpression
| SequenceExpression
export interface CallExpression extends Node {
type: NodeTypes.JS_CALL_EXPRESSION
@ -247,6 +292,7 @@ export interface CallExpression extends Node {
| string
| symbol
| JSChildNode
| SSRCodegenNode
| TemplateChildNode
| TemplateChildNode[])[]
}
@ -269,21 +315,20 @@ export interface ArrayExpression extends Node {
export interface FunctionExpression extends Node {
type: NodeTypes.JS_FUNCTION_EXPRESSION
params: ExpressionNode | ExpressionNode[] | undefined
returns: TemplateChildNode | TemplateChildNode[] | JSChildNode
params: ExpressionNode | string | (ExpressionNode | string)[] | undefined
returns?: TemplateChildNode | TemplateChildNode[] | JSChildNode
body?: BlockStatement | IfStatement
newline: boolean
}
export interface SequenceExpression extends Node {
type: NodeTypes.JS_SEQUENCE_EXPRESSION
expressions: JSChildNode[]
// so that codegen knows it needs to generate ScopeId wrapper
isSlot: boolean
}
export interface ConditionalExpression extends Node {
type: NodeTypes.JS_CONDITIONAL_EXPRESSION
test: ExpressionNode
test: JSChildNode
consequent: JSChildNode
alternate: JSChildNode
newline: boolean
}
export interface CacheExpression extends Node {
@ -293,59 +338,76 @@ export interface CacheExpression extends Node {
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 ----------------------------------------------------------
// createVNode(...)
export interface PlainElementCodegenNode extends CallExpression {
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 interface DirectiveArguments extends ArrayExpression {
elements: DirectiveArgumentNode[]
}
export type ElementCodegenNode =
| PlainElementCodegenNode
| CodegenNodeWithDirective<PlainElementCodegenNode>
// createVNode(...)
export interface PlainComponentCodegenNode extends CallExpression {
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 interface DirectiveArgumentNode extends ArrayExpression {
elements: // dir, exp, arg, modifiers
| [string]
| [string, ExpressionNode]
| [string, ExpressionNode, ExpressionNode]
| [string, ExpressionNode, ExpressionNode, ObjectExpression]
}
export type ComponentCodegenNode =
| PlainComponentCodegenNode
| CodegenNodeWithDirective<PlainComponentCodegenNode>
// renderSlot(...)
export interface RenderSlotCall 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 SlotsExpression = SlotsObjectExpression | DynamicSlotsExpression
@ -397,63 +459,20 @@ export interface DynamicSlotFnProperty extends Property {
value: SlotFunctionExpression
}
// withDirectives(createVNode(...), [
// [_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 type BlockCodegenNode = VNodeCall | RenderSlotCall
export interface IfConditionalExpression extends ConditionalExpression {
consequent: BlockCodegenNode
alternate: BlockCodegenNode | IfConditionalExpression
}
export interface ForCodegenNode extends SequenceExpression {
expressions: [OpenBlockExpression, ForBlockCodegenNode]
}
export interface ForBlockCodegenNode extends CallExpression {
callee: typeof CREATE_BLOCK
arguments: [typeof FRAGMENT, 'null', ForRenderListExpression, string]
export interface ForCodegenNode extends VNodeCall {
isBlock: true
tag: typeof FRAGMENT
props: undefined
children: ForRenderListExpression
patchFlag: string
isForBlock: true
}
export interface ForRenderListExpression extends CallExpression {
@ -465,11 +484,6 @@ export interface ForIteratorExpression extends FunctionExpression {
returns: BlockCodegenNode
}
export interface OpenBlockExpression extends CallExpression {
callee: typeof OPEN_BLOCK
arguments: []
}
// AST Utilities ---------------------------------------------------------------
// 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 }
}
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(
elements: ArrayExpression['elements'],
loc: SourceLocation = locStub
@ -554,15 +625,9 @@ export function createCompoundExpression(
}
}
type InferCodegenNodeType<T> = T extends
| typeof CREATE_VNODE
| typeof CREATE_BLOCK
? PlainElementCodegenNode | PlainComponentCodegenNode
: T extends typeof WITH_DIRECTIVES
?
| CodegenNodeWithDirective<PlainElementCodegenNode>
| CodegenNodeWithDirective<PlainComponentCodegenNode>
: T extends typeof RENDER_SLOT ? SlotOutletCodegenNode : CallExpression
type InferCodegenNodeType<T> = T extends typeof RENDER_SLOT
? RenderSlotCall
: CallExpression
export function createCallExpression<T extends CallExpression['callee']>(
callee: T,
@ -579,8 +644,9 @@ export function createCallExpression<T extends CallExpression['callee']>(
export function createFunctionExpression(
params: FunctionExpression['params'],
returns: FunctionExpression['returns'],
returns: FunctionExpression['returns'] = undefined,
newline: boolean = false,
isSlot: boolean = false,
loc: SourceLocation = locStub
): FunctionExpression {
return {
@ -588,30 +654,23 @@ export function createFunctionExpression(
params,
returns,
newline,
isSlot,
loc
}
}
export function createSequenceExpression(
expressions: SequenceExpression['expressions']
): SequenceExpression {
return {
type: NodeTypes.JS_SEQUENCE_EXPRESSION,
expressions,
loc: locStub
}
}
export function createConditionalExpression(
test: ConditionalExpression['test'],
consequent: ConditionalExpression['consequent'],
alternate: ConditionalExpression['alternate']
alternate: ConditionalExpression['alternate'],
newline = true
): ConditionalExpression {
return {
type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
test,
consequent,
alternate,
newline,
loc: locStub
}
}
@ -629,3 +688,69 @@ export function createCacheExpression(
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
}
}

View File

@ -10,15 +10,21 @@ import {
CallExpression,
ArrayExpression,
ObjectExpression,
SourceLocation,
Position,
InterpolationNode,
CompoundExpressionNode,
SimpleExpressionNode,
FunctionExpression,
SequenceExpression,
ConditionalExpression,
CacheExpression
CacheExpression,
locStub,
SSRCodegenNode,
TemplateLiteral,
IfStatement,
AssignmentExpression,
ReturnStatement,
VNodeCall,
SequenceExpression
} from './ast'
import { SourceMapGenerator, RawSourceMap } from 'source-map'
import {
@ -31,17 +37,25 @@ import {
import { isString, isArray, isSymbol } from '@vue/shared'
import {
helperNameMap,
TO_STRING,
TO_DISPLAY_STRING,
CREATE_VNODE,
RESOLVE_COMPONENT,
RESOLVE_DIRECTIVE,
SET_BLOCK_TRACKING,
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'
import { ImportItem } from './transform'
type CodegenNode = TemplateChildNode | JSChildNode
type CodegenNode = TemplateChildNode | JSChildNode | SSRCodegenNode
export interface CodegenResult {
code: string
@ -58,8 +72,7 @@ export interface CodegenContext extends Required<CodegenOptions> {
indentLevel: number
map?: SourceMapGenerator
helper(key: symbol): string
push(code: string, node?: CodegenNode, openOnly?: boolean): void
resetMapping(loc: SourceLocation): void
push(code: string, node?: CodegenNode): void
indent(): void
deindent(withoutNewLine?: boolean): void
newline(): void
@ -71,7 +84,12 @@ function createCodegenContext(
mode = 'function',
prefixIdentifiers = mode === 'module',
sourceMap = false,
filename = `template.vue.html`
filename = `template.vue.html`,
scopeId = null,
optimizeBindings = false,
runtimeGlobalName = `Vue`,
runtimeModuleName = `vue`,
ssr = false
}: CodegenOptions
): CodegenContext {
const context: CodegenContext = {
@ -79,24 +97,22 @@ function createCodegenContext(
prefixIdentifiers,
sourceMap,
filename,
scopeId,
optimizeBindings,
runtimeGlobalName,
runtimeModuleName,
ssr,
source: ast.loc.source,
code: ``,
column: 1,
line: 1,
offset: 0,
indentLevel: 0,
// lazy require source-map implementation, only in non-browser builds!
map:
__BROWSER__ || !sourceMap
? undefined
: new (loadDep('source-map')).SourceMapGenerator(),
map: undefined,
helper(key) {
const name = helperNameMap[key]
return prefixIdentifiers ? name : `_${name}`
return `_${helperNameMap[key]}`
},
push(code, node, openOnly) {
push(code, node) {
context.code += code
if (!__BROWSER__ && context.map) {
if (node) {
@ -110,16 +126,11 @@ function createCodegenContext(
addMapping(node.loc.start, name)
}
advancePositionWithMutation(context, code)
if (node && !openOnly) {
if (node && node.loc !== locStub) {
addMapping(node.loc.end)
}
}
},
resetMapping(loc: SourceLocation) {
if (!__BROWSER__ && context.map) {
addMapping(loc.start)
}
},
indent() {
newline(++context.indentLevel)
},
@ -154,9 +165,12 @@ function createCodegenContext(
})
}
if (!__BROWSER__ && context.map) {
context.map.setSourceContent(filename, context.source)
if (!__BROWSER__ && sourceMap) {
// 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
}
@ -168,63 +182,37 @@ export function generate(
const {
mode,
push,
helper,
prefixIdentifiers,
indent,
deindent,
newline
newline,
scopeId,
ssr
} = context
const hasHelpers = ast.helpers.length > 0
const useWithBlock = !prefixIdentifiers && mode !== 'module'
const genScopeId = !__BROWSER__ && scopeId != null && mode === 'module'
// preambles
if (mode === 'function') {
// 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 (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 `)
if (!__BROWSER__ && mode === 'module') {
genModulePreamble(ast, context, genScopeId)
} else {
// generate import statements for helpers
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 `)
genFunctionPreamble(ast, context)
}
// 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()
if (useWithBlock) {
push(`with (this) {`)
push(`with (_ctx) {`)
indent()
// function mode const declarations should be inside with block
// also they should be renamed to avoid collision with user properties
@ -234,35 +222,39 @@ export function generate(
.map(s => `${helperNameMap[s]}: _${helperNameMap[s]}`)
.join(', ')} } = _Vue`
)
newline()
if (ast.cached > 0) {
push(`const _cache = $cache`)
newline()
}
push(`\n`)
newline()
}
} else {
push(`const _ctx = this`)
if (ast.cached > 0) {
newline()
push(`const _cache = _ctx.$cache`)
}
newline()
}
// generate asset resolution statements
if (ast.components.length) {
genAssets(ast.components, 'component', context)
if (ast.directives.length || ast.temps > 0) {
newline()
}
}
if (ast.directives.length) {
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()
}
// generate the VNode tree expression
push(`return `)
if (!ssr) {
push(`return `)
}
if (ast.codegenNode) {
genNode(ast.codegenNode, context)
} else {
@ -276,27 +268,164 @@ export function generate(
deindent()
push(`}`)
if (genScopeId && !ssr) {
push(`)`)
}
return {
ast,
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(
assets: string[],
type: 'component' | 'directive',
context: CodegenContext
{ helper, push, newline }: CodegenContext
) {
const resolver = context.helper(
const resolver = helper(
type === 'component' ? RESOLVE_COMPONENT : RESOLVE_DIRECTIVE
)
for (let i = 0; i < assets.length; i++) {
const id = assets[i]
context.push(
push(
`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) {
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) => {
context.push(`const _hoisted_${i + 1} = `)
push(`const _hoisted_${i + 1} = `)
genNode(exp, context)
context.newline()
newline()
})
if (genScopeId) {
push(`${helper(POP_SCOPE_ID)}()`)
newline()
}
}
function genImports(importsOptions: ImportItem[], context: CodegenContext) {
@ -351,7 +495,8 @@ function genNodeListAsArray(
function genNodeList(
nodes: (string | symbol | CodegenNode | TemplateChildNode[])[],
context: CodegenContext,
multilines: boolean = false
multilines: boolean = false,
comma: boolean = true
) {
const { push, newline } = context
for (let i = 0; i < nodes.length; i++) {
@ -365,10 +510,10 @@ function genNodeList(
}
if (i < nodes.length - 1) {
if (multilines) {
push(',')
comma && push(',')
newline()
} else {
push(', ')
comma && push(', ')
}
}
}
@ -413,6 +558,10 @@ function genNode(node: CodegenNode | symbol | string, context: CodegenContext) {
case NodeTypes.COMMENT:
genComment(node, context)
break
case NodeTypes.VNODE_CALL:
genVNodeCall(node, context)
break
case NodeTypes.JS_CALL_EXPRESSION:
genCallExpression(node, context)
break
@ -425,16 +574,37 @@ function genNode(node: CodegenNode | symbol | string, context: CodegenContext) {
case NodeTypes.JS_FUNCTION_EXPRESSION:
genFunctionExpression(node, context)
break
case NodeTypes.JS_SEQUENCE_EXPRESSION:
genSequenceExpression(node, context)
break
case NodeTypes.JS_CONDITIONAL_EXPRESSION:
genConditionalExpression(node, context)
break
case NodeTypes.JS_CACHE_EXPRESSION:
genCacheExpression(node, context)
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 */
case NodeTypes.IF_BRANCH:
// noop
break
default:
if (__DEV__) {
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) {
const { push, helper } = context
push(`${helper(TO_STRING)}(`)
push(`${helper(TO_DISPLAY_STRING)}(`)
genNode(node.content, context)
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
function genCallExpression(node: CallExpression, context: CodegenContext) {
const callee = isString(node.callee)
? node.callee
: context.helper(node.callee)
context.push(callee + `(`, node, true)
context.push(callee + `(`, node)
genNodeList(node.arguments, context)
context.push(`)`)
}
function genObjectExpression(node: ObjectExpression, context: CodegenContext) {
const { push, indent, deindent, newline, resetMapping } = context
const { push, indent, deindent, newline } = context
const { properties } = node
if (!properties.length) {
push(`{}`, node)
@ -529,8 +741,7 @@ function genObjectExpression(node: ObjectExpression, context: CodegenContext) {
push(multilines ? `{` : `{ `)
multilines && indent()
for (let i = 0; i < properties.length; i++) {
const { key, value, loc } = properties[i]
resetMapping(loc) // reset source mapping for every property.
const { key, value } = properties[i]
// key
genExpressionAsPropertyKey(key, context)
push(`: `)
@ -554,8 +765,17 @@ function genFunctionExpression(
node: FunctionExpression,
context: CodegenContext
) {
const { push, indent, deindent } = context
const { params, returns, newline } = node
const { push, indent, deindent, scopeId, mode } = context
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)
if (isArray(params)) {
genNodeList(params, context)
@ -563,27 +783,36 @@ function genFunctionExpression(
genNode(params, context)
}
push(`) => `)
if (newline) {
if (newline || body) {
push(`{`)
indent()
push(`return `)
}
if (isArray(returns)) {
genNodeListAsArray(returns, context)
} else {
genNode(returns, context)
if (returns) {
if (newline) {
push(`return `)
}
if (isArray(returns)) {
genNodeListAsArray(returns, context)
} else {
genNode(returns, context)
}
} else if (body) {
genNode(body, context)
}
if (newline) {
if (newline || body) {
deindent()
push(`}`)
}
if (genScopeId || isSlot) {
push(`)`)
}
}
function genConditionalExpression(
node: ConditionalExpression,
context: CodegenContext
) {
const { test, consequent, alternate } = node
const { test, consequent, alternate, newline: needNewline } = node
const { push, indent, deindent, newline } = context
if (test.type === NodeTypes.SIMPLE_EXPRESSION) {
const needsParens = !isSimpleIdentifier(test.content)
@ -592,15 +821,17 @@ function genConditionalExpression(
needsParens && push(`)`)
} else {
push(`(`)
genCompoundExpression(test, context)
genNode(test, context)
push(`)`)
}
indent()
needNewline && indent()
context.indentLevel++
needNewline || push(` `)
push(`? `)
genNode(consequent, context)
context.indentLevel--
newline()
needNewline && newline()
needNewline || push(` `)
push(`: `)
const isNested = alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION
if (!isNested) {
@ -610,16 +841,7 @@ function genConditionalExpression(
if (!isNested) {
context.indentLevel--
}
deindent(true /* without newline */)
}
function genSequenceExpression(
node: SequenceExpression,
context: CodegenContext
) {
context.push(`(`)
genNodeList(node.expressions, context)
context.push(`)`)
needNewline && deindent(true /* without newline */)
}
function genCacheExpression(node: CacheExpression, context: CodegenContext) {
@ -642,3 +864,77 @@ function genCacheExpression(node: CacheExpression, context: CodegenContext) {
}
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)
}
}

View 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
})
}

View File

@ -16,11 +16,14 @@ export function defaultOnError(error: CompilerError) {
export function createCompilerError<T extends number>(
code: T,
loc?: SourceLocation,
messages?: { [code: number]: string }
messages?: { [code: number]: string },
additionalMessage?: string
): T extends ErrorCodes ? CoreCompilerError : CompilerError {
const msg = __DEV__ || !__BROWSER__ ? (messages || errorMessages)[code] : code
const locInfo = loc ? ` (${loc.start.line}:${loc.start.column})` : ``
const error = new SyntaxError(msg + locInfo) as CompilerError
const msg =
__DEV__ || !__BROWSER__
? (messages || errorMessages)[code] + (additionalMessage || ``)
: code
const error = new SyntaxError(String(msg)) as CompilerError
error.code = code
error.loc = loc
return error as any
@ -58,7 +61,6 @@ export const enum ErrorCodes {
UNEXPECTED_NULL_CHARACTER,
UNEXPECTED_QUESTION_MARK_INSTEAD_OF_TAG_NAME,
UNEXPECTED_SOLIDUS_IN_TAG,
UNKNOWN_NAMED_CHARACTER_REFERENCE,
// Vue-specific parse errors
X_INVALID_END_TAG,
@ -74,19 +76,21 @@ export const enum ErrorCodes {
X_V_BIND_NO_EXPRESSION,
X_V_ON_NO_EXPRESSION,
X_V_SLOT_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET,
X_V_SLOT_NAMED_SLOT_ON_COMPONENT,
X_V_SLOT_MIXED_SLOT_USAGE,
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_MODEL_NO_EXPRESSION,
X_V_MODEL_MALFORMED_EXPRESSION,
X_V_MODEL_ON_SCOPE_VARIABLE,
X_INVALID_EXPRESSION,
X_KEEP_ALIVE_INVALID_CHILDREN,
// generic errors
X_PREFIX_ID_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
// 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]:
"'<?' is allowed only in XML context.",
[ErrorCodes.UNEXPECTED_SOLIDUS_IN_TAG]: "Illegal '/' in tags.",
[ErrorCodes.UNKNOWN_NAMED_CHARACTER_REFERENCE]: 'Unknown entity name.',
// Vue-specific parse errors
[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]:
'Interpolation end sign was not found.',
[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_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_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]:
`Mixed v-slot usage on both the component and nested <template>.` +
`The default slot should also use <template> syntax when there are other ` +
`named slots to avoid scope ambiguity.`,
`When there are multiple named slots, all slots should use <template> ` +
`syntax to avoid scope ambiguity.`,
[ErrorCodes.X_V_SLOT_DUPLICATE_SLOT_NAMES]: `Duplicate slot names found. `,
[ErrorCodes.X_V_SLOT_EXTRANEOUS_NON_SLOT_CHILDREN]:
`Extraneous children found when component has explicit slots. ` +
`These children will be ignored.`,
[ErrorCodes.X_V_SLOT_EXTRANEOUS_DEFAULT_SLOT_CHILDREN]:
`Extraneous children found when component already has explicitly named ` +
`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_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_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
[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.`
}

View File

@ -1,90 +1,20 @@
import { CompilerOptions } from './options'
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
})
}
export { baseCompile } from './compile'
// Also expose lower level APIs & types
export {
CompilerOptions,
ParserOptions,
TransformOptions,
CodegenOptions
CodegenOptions,
HoistTransform
} from './options'
export { parse, TextModes } from './parse'
export { baseParse, TextModes } from './parse'
export {
transform,
createStructuralDirectiveTransform,
TransformContext,
createTransformContext,
traverseNode,
createStructuralDirectiveTransform,
NodeTransform,
StructuralDirectiveTransform,
DirectiveTransform
@ -96,19 +26,32 @@ export {
CompilerError,
createCompilerError
} from './errors'
export * from './ast'
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 { transformOn } from './transforms/vOn'
// utility, but need to rewrite typing to avoid dts relying on @vue/shared
import { generateCodeFrame as _genCodeFrame } from '@vue/shared'
const generateCodeFrame = _genCodeFrame as (
source: string,
start?: number,
end?: number
) => string
export { generateCodeFrame }
export { transformBind } from './transforms/vBind'
export { noopDirectiveTransform } from './transforms/noopDirectiveTransform'
export { processIf } from './transforms/vIf'
export { processFor, createForLoopParams } from './transforms/vFor'
export {
transformExpression,
processExpression
} from './transforms/transformExpression'
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'

View File

@ -1,16 +1,30 @@
import { ElementNode, Namespace } from './ast'
import { ElementNode, Namespace, JSChildNode, PlainElementNode } from './ast'
import { TextModes } from './parse'
import { CompilerError } from './errors'
import { NodeTransform, DirectiveTransform } from './transform'
import {
NodeTransform,
DirectiveTransform,
TransformContext
} from './transform'
import { ParserPlugin } from '@babel/parser'
export interface ParserOptions {
isVoidTag?: (tag: string) => boolean // e.g. img, br, hr
isNativeTag?: (tag: string) => boolean // e.g. loading-indicator in weex
isPreTag?: (tag: string) => boolean // e.g. <pre> where whitespace is intact
isCustomElement?: (tag: string) => boolean
// e.g. platform native elements, e.g. <div> for browsers
isNativeTag?: (tag: string) => boolean
// e.g. native elements that can self-close, e.g. <img>, <br>, <hr>
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
// separate option for end users to extend the native elements list
isCustomElement?: (tag: string) => boolean
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] // ['{{', '}}']
// 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
// to avoid the cost on every parse() call.
maxCRNameLength?: number
onError?: (error: CompilerError) => void
}
export type HoistTransform = (
node: PlainElementNode,
context: TransformContext
) => JSChildNode
export interface TransformOptions {
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
// 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
// Hoist static VNodes and props objects to `_hoisted_x` constants
// Default: false
// - Default: false
hoistStatic?: boolean
// 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.
// e.g `@click="foo"` by default is compiled to `{ onClick: foo }`. With this
// option it's compiled to:
// `{ 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
// 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
}
@ -49,19 +82,26 @@ export interface CodegenOptions {
// - Function mode will generate a single `const { helpers... } = Vue`
// statement and return the render function. It is meant to be used with
// `new Function(code)()` to generate a render function at runtime.
// Default: 'function'
// - Default: '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?
// Default: false
// - Default: false
sourceMap?: boolean
// Filename for source map generation.
// Default: `template.vue.html`
// - Default: `template.vue.html`
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

View File

@ -1,5 +1,5 @@
import { ParserOptions } from './options'
import { NO, isArray } from '@vue/shared'
import { NO, isArray, makeMap } from '@vue/shared'
import { ErrorCodes, createCompilerError, defaultOnError } from './errors'
import {
assert,
@ -21,11 +21,11 @@ import {
SourceLocation,
TextNode,
TemplateChildNode,
InterpolationNode
InterpolationNode,
createRoot
} from './ast'
import { extend } from '@vue/shared'
// `isNativeTag` is optional, others are required
type OptionalOptions = 'isNativeTag' | 'isBuiltInComponent'
type MergedParserOptions = Omit<Required<ParserOptions>, OptionalOptions> &
Pick<ParserOptions, OptionalOptions>
@ -64,25 +64,20 @@ interface ParserContext {
offset: number
line: 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 start = getCursor(context)
return {
type: NodeTypes.ROOT,
children: parseChildren(context, TextModes.DATA, []),
helpers: [],
components: [],
directives: [],
hoists: [],
imports: [],
cached: 0,
codegenNode: undefined,
loc: getSelection(context, start)
}
return createRoot(
parseChildren(context, TextModes.DATA, []),
getSelection(context, start)
)
}
function createParserContext(
@ -99,7 +94,8 @@ function createParserContext(
offset: 0,
originalSource: content,
source: content,
inPre: false
inPre: false,
inVPre: false
}
}
@ -117,11 +113,11 @@ function parseChildren(
const s = context.source
let node: TemplateChildNode | TemplateChildNode[] | undefined = undefined
if (mode === TextModes.DATA) {
if (!context.inPre && startsWith(s, context.options.delimiters[0])) {
if (mode === TextModes.DATA || mode === TextModes.RCDATA) {
if (!context.inVPre && startsWith(s, context.options.delimiters[0])) {
// '{{'
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
if (s.length === 1) {
emitError(context, ErrorCodes.EOF_BEFORE_TAG_NAME, 1)
@ -193,45 +189,51 @@ function parseChildren(
// Whitespace management for more efficient output
// (same as v2 whitespace: 'condense')
let removedWhitespace = false
if (
mode !== TextModes.RAWTEXT &&
(!parent || !context.options.isPreTag(parent.tag))
) {
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i]
if (node.type === NodeTypes.TEXT) {
if (!node.content.trim()) {
const prev = nodes[i - 1]
const next = nodes[i + 1]
// If:
// - the whitespace is the first or last node, or:
// - the whitespace is adjacent to a comment, or:
// - the whitespace is between two elements AND contains newline
// Then the whitespace is ignored.
if (
!prev ||
!next ||
prev.type === NodeTypes.COMMENT ||
next.type === NodeTypes.COMMENT ||
(prev.type === NodeTypes.ELEMENT &&
next.type === NodeTypes.ELEMENT &&
/[\r\n]/.test(node.content))
) {
removedWhitespace = true
nodes[i] = null as any
if (mode !== TextModes.RAWTEXT) {
if (!context.inPre) {
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i]
if (node.type === NodeTypes.TEXT) {
if (!node.content.trim()) {
const prev = nodes[i - 1]
const next = nodes[i + 1]
// If:
// - the whitespace is the first or last node, or:
// - the whitespace is adjacent to a comment, or:
// - the whitespace is between two elements AND contains newline
// Then the whitespace is ignored.
if (
!prev ||
!next ||
prev.type === NodeTypes.COMMENT ||
next.type === NodeTypes.COMMENT ||
(prev.type === NodeTypes.ELEMENT &&
next.type === NodeTypes.ELEMENT &&
/[\r\n]/.test(node.content))
) {
removedWhitespace = true
nodes[i] = null as any
} else {
// Otherwise, condensed consecutive whitespace inside the text down to
// a single space
node.content = ' '
}
} else {
// Otherwise, condensed consecutive whitespace inside the text down to
// a single space
node.content = ' '
node.content = node.content.replace(/\s+/g, ' ')
}
} 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 {
@ -353,9 +355,11 @@ function parseElement(
// Start tag.
const wasInPre = context.inPre
const wasInVPre = context.inVPre
const parent = last(ancestors)
const element = parseTag(context, TagType.Start, parent)
const isPreBoundary = context.inPre && !wasInPre
const isVPreBoundary = context.inVPre && !wasInVPre
if (element.isSelfClosing || context.options.isVoidTag(element.tag)) {
return element
@ -363,7 +367,7 @@ function parseElement(
// Children.
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)
ancestors.pop()
@ -373,7 +377,7 @@ function parseElement(
if (startsWithEndTagOpen(context.source, element.tag)) {
parseTag(context, TagType.End, parent)
} 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') {
const first = children[0]
if (first && startsWith(first.loc.source, '<!--')) {
@ -387,6 +391,9 @@ function parseElement(
if (isPreBoundary) {
context.inPre = false
}
if (isVPreBoundary) {
context.inVPre = false
}
return element
}
@ -395,6 +402,10 @@ const enum TagType {
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).
*/
@ -425,12 +436,17 @@ function parseTag(
// Attributes.
let props = parseAttributes(context, type)
// check <pre> tag
if (context.options.isPreTag(tag)) {
context.inPre = true
}
// check v-pre
if (
!context.inPre &&
!context.inVPre &&
props.some(p => p.type === NodeTypes.DIRECTIVE && p.name === 'pre')
) {
context.inPre = true
context.inVPre = true
// reset context
extend(context, cursor)
context.source = currentSource
@ -452,20 +468,32 @@ function parseTag(
let tagType = ElementTypes.ELEMENT
const options = context.options
if (!context.inPre && !options.isCustomElement(tag)) {
if (options.isNativeTag) {
if (!context.inVPre && !options.isCustomElement(tag)) {
const hasVIs = props.some(
p => p.type === NodeTypes.DIRECTIVE && p.name === 'is'
)
if (options.isNativeTag && !hasVIs) {
if (!options.isNativeTag(tag)) tagType = ElementTypes.COMPONENT
} else if (
hasVIs ||
isCoreComponent(tag) ||
(options.isBuiltInComponent && options.isBuiltInComponent(tag)) ||
/^[A-Z]/.test(tag)
/^[A-Z]/.test(tag) ||
tag === 'component'
) {
tagType = ElementTypes.COMPONENT
}
if (tag === '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
}
}
@ -539,7 +567,7 @@ function parseAttribute(
{
const pattern = /["'<]/g
let m: RegExpExecArray | null
while ((m = pattern.exec(name)) !== null) {
while ((m = pattern.exec(name))) {
emitError(
context,
ErrorCodes.UNEXPECTED_CHARACTER_IN_ATTRIBUTE_NAME,
@ -570,7 +598,7 @@ function parseAttribute(
}
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(
name
)!
@ -688,9 +716,9 @@ function parseAttributeValue(
if (!match) {
return undefined
}
let unexpectedChars = /["'<=`]/g
const unexpectedChars = /["'<=`]/g
let m: RegExpExecArray | null
while ((m = unexpectedChars.exec(match[0])) !== null) {
while ((m = unexpectedChars.exec(match[0]))) {
emitError(
context,
ErrorCodes.UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE,
@ -818,8 +846,8 @@ function parseTextData(
if (head[0] === '&') {
// Named character reference.
let name = '',
value: string | undefined = undefined
let name = ''
let value: string | undefined = undefined
if (/[0-9a-z]/i.test(rawText[1])) {
for (
let length = context.options.maxCRNameLength;
@ -834,7 +862,7 @@ function parseTextData(
if (
mode === TextModes.ATTRIBUTE_VALUE &&
!semi &&
/[=a-z0-9]/i.test(rawText[1 + name.length] || '')
/[=a-z0-9]/i.test(rawText[name.length + 1] || '')
) {
decodedText += '&' + name
advance(1 + name.length)
@ -849,7 +877,6 @@ function parseTextData(
}
}
} else {
emitError(context, ErrorCodes.UNKNOWN_NAMED_CHARACTER_REFERENCE)
decodedText += '&' + name
advance(1 + name.length)
}
@ -964,9 +991,9 @@ function getNewPosition(
function emitError(
context: ParserContext,
code: ErrorCodes,
offset?: number
offset?: number,
loc: Position = getCursor(context)
): void {
const loc = getCursor(context)
if (offset) {
loc.offset += offset
loc.column += offset

View File

@ -1,5 +1,5 @@
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 KEEP_ALIVE = Symbol(__DEV__ ? `KeepAlive` : ``)
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_COMMENT = Symbol(__DEV__ ? `createCommentVNode` : ``)
export const CREATE_TEXT = Symbol(__DEV__ ? `createTextVNode` : ``)
export const CREATE_STATIC = Symbol(__DEV__ ? `createStaticVNode` : ``)
export const RESOLVE_COMPONENT = Symbol(__DEV__ ? `resolveComponent` : ``)
export const RESOLVE_DYNAMIC_COMPONENT = Symbol(
__DEV__ ? `resolveDynamicComponent` : ``
@ -17,18 +18,22 @@ export const WITH_DIRECTIVES = Symbol(__DEV__ ? `withDirectives` : ``)
export const RENDER_LIST = Symbol(__DEV__ ? `renderList` : ``)
export const RENDER_SLOT = Symbol(__DEV__ ? `renderSlot` : ``)
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 TO_HANDLERS = Symbol(__DEV__ ? `toHandlers` : ``)
export const CAMELIZE = Symbol(__DEV__ ? `camelize` : ``)
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
// generated code. Make sure these are correctly exported in the runtime!
// Using `any` here because TS doesn't allow symbols as index type.
export const helperNameMap: any = {
[FRAGMENT]: `Fragment`,
[PORTAL]: `Portal`,
[TELEPORT]: `Teleport`,
[SUSPENSE]: `Suspense`,
[KEEP_ALIVE]: `KeepAlive`,
[BASE_TRANSITION]: `BaseTransition`,
@ -37,6 +42,7 @@ export const helperNameMap: any = {
[CREATE_VNODE]: `createVNode`,
[CREATE_COMMENT]: `createCommentVNode`,
[CREATE_TEXT]: `createTextVNode`,
[CREATE_STATIC]: `createStaticVNode`,
[RESOLVE_COMPONENT]: `resolveComponent`,
[RESOLVE_DYNAMIC_COMPONENT]: `resolveDynamicComponent`,
[RESOLVE_DIRECTIVE]: `resolveDirective`,
@ -44,11 +50,15 @@ export const helperNameMap: any = {
[RENDER_LIST]: `renderList`,
[RENDER_SLOT]: `renderSlot`,
[CREATE_SLOTS]: `createSlots`,
[TO_STRING]: `toString`,
[TO_DISPLAY_STRING]: `toDisplayString`,
[MERGE_PROPS]: `mergeProps`,
[TO_HANDLERS]: `toHandlers`,
[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) {

View File

@ -12,11 +12,10 @@ import {
JSChildNode,
SimpleExpressionNode,
ElementTypes,
ElementCodegenNode,
ComponentCodegenNode,
createCallExpression,
CacheExpression,
createCacheExpression
createCacheExpression,
TemplateLiteral,
createVNodeCall
} from './ast'
import {
isString,
@ -27,14 +26,14 @@ import {
} from '@vue/shared'
import { defaultOnError } from './errors'
import {
TO_STRING,
TO_DISPLAY_STRING,
FRAGMENT,
helperNameMap,
WITH_DIRECTIVES,
CREATE_BLOCK,
CREATE_COMMENT
CREATE_COMMENT,
OPEN_BLOCK
} from './runtimeHelpers'
import { isVSlot, createBlockExpression } from './utils'
import { isVSlot } from './utils'
import { hoistStatic, isSingleElementRoot } from './transforms/hoistStatic'
// There are two types of transforms:
@ -61,7 +60,8 @@ export type DirectiveTransform = (
export interface DirectiveTransformResult {
props: Property[]
needRuntime: boolean | symbol
needRuntime?: boolean | symbol
ssrTagParts?: TemplateLiteral['elements']
}
// A structural directive transform is a technically a NodeTransform;
@ -84,6 +84,7 @@ export interface TransformContext extends Required<TransformOptions> {
directives: Set<string>
hoists: JSChildNode[]
imports: Set<ImportItem>
temps: number
cached: number
identifiers: { [name: string]: number | undefined }
scopes: {
@ -106,7 +107,7 @@ export interface TransformContext extends Required<TransformOptions> {
cache<T extends JSChildNode>(exp: T, isVNode?: boolean): CacheExpression | T
}
function createTransformContext(
export function createTransformContext(
root: RootNode,
{
prefixIdentifiers = false,
@ -114,17 +115,36 @@ function createTransformContext(
cacheHandlers = false,
nodeTransforms = [],
directiveTransforms = {},
transformHoist = null,
isBuiltInComponent = NOOP,
expressionPlugins = [],
scopeId = null,
ssr = false,
onError = defaultOnError
}: TransformOptions
): TransformContext {
const context: TransformContext = {
// options
prefixIdentifiers,
hoistStatic,
cacheHandlers,
nodeTransforms,
directiveTransforms,
transformHoist,
isBuiltInComponent,
expressionPlugins,
scopeId,
ssr,
onError,
// state
root,
helpers: new Set(),
components: new Set(),
directives: new Set(),
hoists: [],
imports: new Set(),
temps: 0,
cached: 0,
identifiers: {},
scopes: {
@ -133,25 +153,17 @@ function createTransformContext(
vPre: 0,
vOnce: 0
},
prefixIdentifiers,
hoistStatic,
cacheHandlers,
nodeTransforms,
directiveTransforms,
isBuiltInComponent,
onError,
parent: null,
currentNode: root,
childIndex: 0,
// methods
helper(name) {
context.helpers.add(name)
return name
},
helperString(name) {
return (
(context.prefixIdentifiers ? `` : `_`) +
helperNameMap[context.helper(name)]
)
return `_${helperNameMap[context.helper(name)]}`
},
replaceNode(node) {
/* istanbul ignore if */
@ -251,10 +263,20 @@ export function transform(root: RootNode, options: TransformOptions) {
if (options.hoistStatic) {
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 { children } = root
const child = children[0]
@ -263,20 +285,13 @@ function finalizeRoot(root: RootNode, context: TransformContext) {
if (isSingleElementRoot(root, child) && child.codegenNode) {
// single element root is never hoisted so codegenNode will never be
// SimpleExpressionNode
const codegenNode = child.codegenNode as
| ElementCodegenNode
| ComponentCodegenNode
| CacheExpression
if (codegenNode.type !== NodeTypes.JS_CACHE_EXPRESSION) {
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
const codegenNode = child.codegenNode
if (codegenNode.type === NodeTypes.VNODE_CALL) {
codegenNode.isBlock = true
helper(OPEN_BLOCK)
helper(CREATE_BLOCK)
}
root.codegenNode = codegenNode
} else {
// - single <slot/>, IfNode, ForNode: already blocks.
// - single text node: always patched.
@ -285,27 +300,21 @@ function finalizeRoot(root: RootNode, context: TransformContext) {
}
} else if (children.length > 1) {
// root has multiple nodes - return a fragment block.
root.codegenNode = createBlockExpression(
createCallExpression(helper(CREATE_BLOCK), [
helper(FRAGMENT),
`null`,
root.children,
`${PatchFlags.STABLE_FRAGMENT} /* ${
PatchFlagNames[PatchFlags.STABLE_FRAGMENT]
} */`
]),
context
root.codegenNode = createVNodeCall(
context,
helper(FRAGMENT),
undefined,
root.children,
`${PatchFlags.STABLE_FRAGMENT} /* ${
PatchFlagNames[PatchFlags.STABLE_FRAGMENT]
} */`,
undefined,
undefined,
true
)
} else {
// 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(
@ -319,7 +328,6 @@ export function traverseChildren(
for (; i < parent.children.length; i++) {
const child = parent.children[i]
if (isString(child)) continue
context.currentNode = child
context.parent = parent
context.childIndex = i
context.onNodeRemoved = nodeRemoved
@ -331,6 +339,7 @@ export function traverseNode(
node: RootNode | TemplateChildNode,
context: TransformContext
) {
context.currentNode = node
// apply transform plugins
const { nodeTransforms } = context
const exitFns = []
@ -354,21 +363,26 @@ export function traverseNode(
switch (node.type) {
case NodeTypes.COMMENT:
// inject import for the Comment symbol, which is needed for creating
// comment nodes with `createVNode`
context.helper(CREATE_COMMENT)
if (!context.ssr) {
// inject import for the Comment symbol, which is needed for creating
// comment nodes with `createVNode`
context.helper(CREATE_COMMENT)
}
break
case NodeTypes.INTERPOLATION:
// no need to traverse, but we need to inject toString helper
context.helper(TO_STRING)
if (!context.ssr) {
context.helper(TO_DISPLAY_STRING)
}
break
// for container types, further traverse downwards
case NodeTypes.IF:
for (let i = 0; i < node.branches.length; i++) {
traverseChildren(node.branches[i], context)
traverseNode(node.branches[i], context)
}
break
case NodeTypes.IF_BRANCH:
case NodeTypes.FOR:
case NodeTypes.ELEMENT:
case NodeTypes.ROOT:

View File

@ -8,11 +8,9 @@ import {
ComponentNode,
TemplateNode,
ElementNode,
PlainElementCodegenNode,
CodegenNodeWithDirective
VNodeCall
} from '../ast'
import { TransformContext } from '../transform'
import { WITH_DIRECTIVES } from '../runtimeHelpers'
import { PatchFlags, isString, isSymbol } from '@vue/shared'
import { isSlotOutlet, findProp } from '../utils'
@ -21,6 +19,8 @@ export function hoistStatic(root: RootNode, context: TransformContext) {
root.children,
context,
new Map(),
// Root node is unfortuantely non-hoistable due to potential parent
// fallthrough attributes.
isSingleElementRoot(root, root.children[0])
)
}
@ -52,13 +52,18 @@ function walk(
) {
if (!doNotHoistNode && isStaticNode(child, resultCache)) {
// 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
} else {
// node may contain dynamic children, but its props may be eligible for
// hoisting.
const codegenNode = child.codegenNode!
if (codegenNode.type === NodeTypes.JS_CALL_EXPRESSION) {
if (codegenNode.type === NodeTypes.VNODE_CALL) {
const flag = getPatchFlag(codegenNode)
if (
(!flag ||
@ -68,8 +73,8 @@ function walk(
!hasCachedProps(child)
) {
const props = getNodeProps(child)
if (props && props !== `null`) {
getVNodeCall(codegenNode).arguments[1] = context.hoist(props)
if (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
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
}
const codegenNode = node.codegenNode!
if (codegenNode.type !== NodeTypes.JS_CALL_EXPRESSION) {
if (codegenNode.type !== NodeTypes.VNODE_CALL) {
return false
}
const flag = getPatchFlag(codegenNode)
@ -116,6 +126,12 @@ export function isStaticNode(
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)
return true
} else {
@ -127,6 +143,7 @@ export function isStaticNode(
return true
case NodeTypes.IF:
case NodeTypes.FOR:
case NodeTypes.IF_BRANCH:
return false
case NodeTypes.INTERPOLATION:
case NodeTypes.TEXT_CALL:
@ -157,14 +174,20 @@ function hasCachedProps(node: PlainElementNode): boolean {
return false
}
const props = getNodeProps(node)
if (
props &&
props !== 'null' &&
props.type === NodeTypes.JS_OBJECT_EXPRESSION
) {
if (props && props.type === NodeTypes.JS_OBJECT_EXPRESSION) {
const { properties } = props
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
}
}
@ -174,30 +197,12 @@ function hasCachedProps(node: PlainElementNode): boolean {
function getNodeProps(node: PlainElementNode) {
const codegenNode = node.codegenNode!
if (codegenNode.type === NodeTypes.JS_CALL_EXPRESSION) {
return getVNodeArgAt(
codegenNode,
1
) as PlainElementCodegenNode['arguments'][1]
if (codegenNode.type === NodeTypes.VNODE_CALL) {
return codegenNode.props
}
}
type NonCachedCodegenNode =
| PlainElementCodegenNode
| 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
function getPatchFlag(node: VNodeCall): number | undefined {
const flag = node.patchFlag
return flag ? parseInt(flag, 10) : undefined
}

View File

@ -0,0 +1,3 @@
import { DirectiveTransform } from '../transform'
export const noopDirectiveTransform: DirectiveTransform = () => ({ props: [] })

View File

@ -13,27 +13,31 @@ import {
createObjectProperty,
createSimpleExpression,
createObjectExpression,
Property
Property,
ComponentNode,
VNodeCall,
TemplateTextChildNode,
DirectiveArguments,
createVNodeCall
} from '../ast'
import { PatchFlags, PatchFlagNames, isSymbol } from '@vue/shared'
import { PatchFlags, PatchFlagNames, isSymbol, isOn } from '@vue/shared'
import { createCompilerError, ErrorCodes } from '../errors'
import {
CREATE_VNODE,
WITH_DIRECTIVES,
RESOLVE_DIRECTIVE,
RESOLVE_COMPONENT,
RESOLVE_DYNAMIC_COMPONENT,
MERGE_PROPS,
TO_HANDLERS,
PORTAL,
TELEPORT,
KEEP_ALIVE
} from '../runtimeHelpers'
import {
getInnerRange,
isVSlot,
toValidAssetId,
findProp,
isCoreComponent
isCoreComponent,
isBindKey,
findDir
} from '../utils'
import { buildSlots } from './vSlot'
import { isStaticNode } from './hoistStatic'
@ -45,107 +49,97 @@ const directiveImportMap = new WeakMap<DirectiveNode, symbol>()
// generate a JavaScript AST for this element's codegen
export const transformElement: NodeTransform = (node, context) => {
if (
node.type !== NodeTypes.ELEMENT ||
// handled by transformSlotOutlet
node.tagType === ElementTypes.SLOT ||
// <template v-if/v-for> should have already been replaced
// <template v-slot> is handled by buildSlots
(node.tagType === ElementTypes.TEMPLATE && node.props.some(isVSlot))
!(
node.type === NodeTypes.ELEMENT &&
(node.tagType === ElementTypes.ELEMENT ||
node.tagType === ElementTypes.COMPONENT)
)
) {
return
}
// perform the work on exit, after all child expressions have been
// processed and merged.
return function postTransformElement() {
const { tag, tagType, props } = node
const builtInComponentSymbol =
isCoreComponent(tag) || context.isBuiltInComponent(tag)
const isComponent = tagType === ElementTypes.COMPONENT
const { tag, props } = node
const isComponent = node.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 runtimeDirectives: DirectiveNode[] | undefined
let vnodeDynamicProps: VNodeCall['dynamicProps']
let dynamicPropNames: string[] | undefined
let dynamicComponent: string | CallExpression | undefined
let vnodeDirectives: VNodeCall['directives']
// handle dynamic component
const isProp = findProp(node, 'is')
if (tag === 'component') {
if (isProp) {
// static <component is="foo" />
if (isProp.type === NodeTypes.ATTRIBUTE) {
const tag = isProp.value && isProp.value.content
if (tag) {
context.helper(RESOLVE_COMPONENT)
context.components.add(tag)
dynamicComponent = toValidAssetId(tag, `component`)
}
}
// dynamic <component :is="asdf" />
else if (isProp.exp) {
dynamicComponent = createCallExpression(
context.helper(RESOLVE_DYNAMIC_COMPONENT),
// _ctx.$ exposes the owner instance of current render function
[isProp.exp, context.prefixIdentifiers ? `_ctx.$` : `$`]
let shouldUseBlock =
!isComponent &&
// <svg> and <foreignObject> must be forced into blocks so that block
// updates inside get proper isSVG flag at runtime. (#639, #643)
// This is technically web-specific, but splitting the logic out of core
// leads to too much unnecessary complexity.
(tag === 'svg' ||
tag === 'foreignObject' ||
// #938: elements with dynamic keys should be forced into blocks
findProp(node, 'key', true))
// props
if (props.length > 0) {
const propsBuildResult = buildProps(node, context)
vnodeProps = propsBuildResult.props
patchFlag = propsBuildResult.patchFlag
dynamicPropNames = propsBuildResult.dynamicPropNames
const directives = propsBuildResult.directives
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
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 (
const shouldBuildAsSlots =
isComponent &&
builtInComponentSymbol !== PORTAL &&
builtInComponentSymbol !== KEEP_ALIVE
) {
// Teleport is not a real component and has dedicated runtime handling
vnodeTag !== TELEPORT &&
// explained above.
vnodeTag !== KEEP_ALIVE
if (shouldBuildAsSlots) {
const { slots, hasDynamicSlots } = buildSlots(node, context)
args.push(slots)
vnodeChildren = slots
if (hasDynamicSlots) {
patchFlag |= PatchFlags.DYNAMIC_SLOTS
}
} else if (node.children.length === 1) {
} else if (node.children.length === 1 && vnodeTag !== TELEPORT) {
const child = node.children[0]
const type = child.type
// 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
// (plain / interpolation / expression)
if (hasDynamicTextChild || type === NodeTypes.TEXT) {
args.push(child)
vnodeChildren = child as TemplateTextChildNode
} else {
args.push(node.children)
vnodeChildren = node.children
}
} else {
args.push(node.children)
vnodeChildren = node.children
}
}
// patchFlag & dynamicPropNames
if (patchFlag !== 0) {
if (!hasChildren) {
if (!hasProps) {
args.push(`null`)
}
args.push(`null`)
}
if (__DEV__) {
const flagNames = Object.keys(PatchFlagNames)
.map(Number)
.filter(n => n > 0 && patchFlag & n)
.map(n => PatchFlagNames[n])
.join(`, `)
args.push(patchFlag + ` /* ${flagNames} */`)
if (patchFlag < 0) {
// special flags (negative and mutually exclusive)
vnodePatchFlag = patchFlag + ` /* ${PatchFlagNames[patchFlag]} */`
} else {
// bitwise flags
const flagNames = Object.keys(PatchFlagNames)
.map(Number)
.filter(n => n > 0 && patchFlag & n)
.map(n => PatchFlagNames[n])
.join(`, `)
vnodePatchFlag = patchFlag + ` /* ${flagNames} */`
}
} else {
args.push(patchFlag + '')
vnodePatchFlag = String(patchFlag)
}
if (dynamicPropNames && dynamicPropNames.length) {
args.push(stringifyDynamicPropNames(dynamicPropNames))
vnodeDynamicProps = stringifyDynamicPropNames(dynamicPropNames)
}
}
const { loc } = node
const vnode = createCallExpression(context.helper(CREATE_VNODE), args, loc)
if (runtimeDirectives && runtimeDirectives.length) {
node.codegenNode = createCallExpression(
context.helper(WITH_DIRECTIVES),
[
vnode,
createArrayExpression(
runtimeDirectives.map(dir => buildDirectiveArgs(dir, context)),
loc
)
],
loc
)
} else {
node.codegenNode = vnode
}
node.codegenNode = createVNodeCall(
context,
vnodeTag,
vnodeProps,
vnodeChildren,
vnodePatchFlag,
vnodeDynamicProps,
vnodeDirectives,
!!shouldUseBlock,
false /* isForBlock */,
node.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 += ', '
export function resolveComponentType(
node: ComponentNode,
context: TransformContext,
ssr = false
) {
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
@ -224,14 +241,15 @@ export type PropsExpression = ObjectExpression | CallExpression | ExpressionNode
export function buildProps(
node: ElementNode,
context: TransformContext,
props: ElementNode['props'] = node.props
props: ElementNode['props'] = node.props,
ssr = false
): {
props: PropsExpression | undefined
directives: DirectiveNode[]
patchFlag: number
dynamicPropNames: string[]
} {
const elementLoc = node.loc
const { tag, loc: elementLoc } = node
const isComponent = node.tagType === ElementTypes.COMPONENT
let properties: ObjectExpression['properties'] = []
const mergeArgs: PropsExpression[] = []
@ -242,27 +260,40 @@ export function buildProps(
let hasRef = false
let hasClassBinding = false
let hasStyleBinding = false
let hasHydrationEventBinding = false
let hasDynamicKeys = false
const dynamicPropNames: string[] = []
const analyzePatchFlag = ({ key, value }: Property) => {
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 (
value.type === NodeTypes.JS_CACHE_EXPRESSION ||
((value.type === NodeTypes.SIMPLE_EXPRESSION ||
value.type === NodeTypes.COMPOUND_EXPRESSION) &&
isStaticNode(value))
) {
// skip if the prop is a cached handler or has constant value
return
}
const name = key.content
if (name === 'ref') {
hasRef = true
} else if (name === 'class') {
hasClassBinding = true
} else if (name === 'style') {
hasStyleBinding = true
} else if (name !== 'key') {
} else if (name !== 'key' && !dynamicPropNames.includes(name)) {
dynamicPropNames.push(name)
}
} else {
@ -278,6 +309,10 @@ export function buildProps(
if (name === 'ref') {
hasRef = true
}
// skip :is on <component>
if (name === 'is' && tag === 'component') {
continue
}
properties.push(
createObjectProperty(
createSimpleExpression(
@ -295,6 +330,8 @@ export function buildProps(
} else {
// directives
const { name, arg, exp, loc } = prop
const isBind = name === 'bind'
const isOn = name === 'on'
// skip v-slot - it is handled by its dedicated transform.
if (name === 'slot') {
@ -305,15 +342,23 @@ export function buildProps(
}
continue
}
// skip v-once - it is handled by its dedicated transform.
if (name === 'once') {
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
const isBind = name === 'bind'
const isOn = name === 'on'
if (!arg && (isBind || isOn)) {
hasDynamicKeys = true
if (exp) {
@ -351,7 +396,7 @@ export function buildProps(
if (directiveTransform) {
// has built-in directive transform.
const { props, needRuntime } = directiveTransform(prop, node, context)
props.forEach(analyzePatchFlag)
!ssr && props.forEach(analyzePatchFlag)
properties.push(...props)
if (needRuntime) {
runtimeDirectives.push(prop)
@ -405,8 +450,14 @@ export function buildProps(
if (dynamicPropNames.length) {
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
}
@ -437,12 +488,7 @@ function dedupeProperties(properties: Property[]): Property[] {
const name = prop.key.content
const existing = knownProps.get(name)
if (existing) {
if (
name === 'style' ||
name === 'class' ||
name.startsWith('on') ||
name.startsWith('vnode')
) {
if (name === 'style' || name === 'class' || name.startsWith('on')) {
mergeAsArray(existing, prop)
}
// unexpected duplicate, should have emitted error during parse
@ -472,7 +518,6 @@ function buildDirectiveArgs(
const dirArgs: ArrayExpression['elements'] = []
const runtime = directiveImportMap.get(dir)
if (runtime) {
context.helper(runtime)
dirArgs.push(context.helperString(runtime))
} else {
// inject statement for resolving directive
@ -507,3 +552,12 @@ function buildDirectiveArgs(
}
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 + `]`
}

View File

@ -16,7 +16,6 @@ import {
CompoundExpressionNode,
createCompoundExpression
} from '../ast'
import { Node, Function, Identifier, Property } from 'estree'
import {
advancePositionWithClone,
isSimpleIdentifier,
@ -25,6 +24,7 @@ import {
} from '../utils'
import { isGloballyWhitelisted, makeMap } from '@vue/shared'
import { createCompilerError, ErrorCodes } from '../errors'
import { Node, Function, Identifier, ObjectProperty } from '@babel/types'
const isLiteralWhitelisted = /*#__PURE__*/ makeMap('true,false,null,this')
@ -40,11 +40,15 @@ export const transformExpression: NodeTransform = (node, context) => {
const dir = node.props[i]
// do not process for v-on & v-for since they are special handled
if (dir.type === NodeTypes.DIRECTIVE && dir.name !== 'for') {
const exp = dir.exp as SimpleExpressionNode | undefined
const arg = dir.arg as SimpleExpressionNode | undefined
const exp = dir.exp
const arg = dir.arg
// do not process exp if this is v-on:arg - we need special handling
// for wrapping inline statements.
if (exp && !(dir.name === 'on' && arg)) {
if (
exp &&
exp.type === NodeTypes.SIMPLE_EXPRESSION &&
!(dir.name === 'on' && arg)
) {
dir.exp = processExpression(
exp,
context,
@ -52,7 +56,7 @@ export const transformExpression: NodeTransform = (node, context) => {
dir.name === 'slot'
)
}
if (arg && !arg.isStatic) {
if (arg && arg.type === NodeTypes.SIMPLE_EXPRESSION && !arg.isStatic) {
dir.arg = processExpression(arg, context)
}
}
@ -76,7 +80,9 @@ export function processExpression(
context: TransformContext,
// some expressions like v-slot props & v-for aliases should be parsed as
// function params
asParams: boolean = false
asParams = false,
// v-on handler values may contain multiple statements
asRawStatements = false
): ExpressionNode {
if (!context.prefixIdentifiers || !node.content.trim()) {
return node
@ -84,6 +90,8 @@ export function processExpression(
// fast path if expression is a simple identifier.
const rawExp = node.content
// bail on parens to prevent any possible function invocations.
const bailConstant = rawExp.indexOf(`(`) > -1
if (isSimpleIdentifier(rawExp)) {
if (
!asParams &&
@ -92,7 +100,7 @@ export function processExpression(
!isLiteralWhitelisted(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
node.isConstant = true
}
@ -100,26 +108,48 @@ export function processExpression(
}
let ast: any
// if the expression is supposed to be used in a function params position
// we need to parse it differently.
const source = `(${rawExp})${asParams ? `=>{}` : ``}`
// exp needs to be parsed differently:
// 1. Multiple inline statements (v-on, with presence of `;`): parse as raw
// 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 {
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) {
context.onError(
createCompilerError(ErrorCodes.X_INVALID_EXPRESSION, node.loc)
createCompilerError(
ErrorCodes.X_INVALID_EXPRESSION,
node.loc,
undefined,
e.message
)
)
return node
}
const ids: (Identifier & PrefixMeta)[] = []
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.`.
walkJS(ast, {
enter(node: Node & PrefixMeta, parent) {
if (node.type === 'Identifier') {
if (!ids.includes(node)) {
if (!isDuplicate(node)) {
const needPrefix = shouldPrefix(node, parent)
if (!knownIds[node.name] && needPrefix) {
if (isPropertyShorthand(node, parent)) {
@ -128,12 +158,13 @@ export function processExpression(
node.prefix = `${node.name}: `
}
node.name = `_ctx.${node.name}`
node.isConstant = false
ids.push(node)
} else if (!isStaticPropertyKey(node, parent)) {
// The identifier is considered constant unless it's pointing to a
// 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
// source map support. (except for property keys which are static)
ids.push(node)
@ -223,7 +254,7 @@ export function processExpression(
ret = createCompoundExpression(children, node.loc)
} else {
ret = node
ret.isConstant = true
ret.isConstant = !bailConstant
}
ret.identifiers = Object.keys(knownIds)
return ret
@ -232,17 +263,21 @@ export function processExpression(
const isFunction = (node: Node): node is Function =>
/Function(Expression|Declaration)$/.test(node.type)
const isPropertyKey = (node: Node, parent: Node) =>
parent &&
parent.type === 'Property' &&
parent.key === node &&
!parent.computed
const isStaticProperty = (node: Node): node is ObjectProperty =>
node && node.type === 'ObjectProperty' && !node.computed
const isPropertyShorthand = (node: Node, parent: Node) =>
isPropertyKey(node, parent) && (parent as Property).value === node
const isPropertyShorthand = (node: Node, parent: 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) =>
isPropertyKey(node, parent) && (parent as Property).value !== node
isStaticProperty(parent) && parent.key === node
function shouldPrefix(identifier: Identifier, parent: Node) {
if (
@ -257,7 +292,8 @@ function shouldPrefix(identifier: Identifier, parent: Node) {
!isStaticPropertyKey(identifier, parent) &&
// not a property of a MemberExpression
!(
parent.type === 'MemberExpression' &&
(parent.type === 'MemberExpression' ||
parent.type === 'OptionalMemberExpression') &&
parent.property === identifier &&
!parent.computed
) &&

View File

@ -1,78 +1,32 @@
import { NodeTransform } from '../transform'
import { NodeTransform, TransformContext } from '../transform'
import {
NodeTypes,
CallExpression,
createCallExpression,
ExpressionNode
ExpressionNode,
SlotOutletNode
} from '../ast'
import { isSlotOutlet } from '../utils'
import { buildProps } from './transformElement'
import { isSlotOutlet, findProp } from '../utils'
import { buildProps, PropsExpression } from './transformElement'
import { createCompilerError, ErrorCodes } from '../errors'
import { RENDER_SLOT } from '../runtimeHelpers'
export const transformSlotOutlet: NodeTransform = (node, context) => {
if (isSlotOutlet(node)) {
const { props, children, loc } = node
const $slots = context.prefixIdentifiers ? `_ctx.$slots` : `$slots`
let slotName: string | ExpressionNode = `"default"`
const { children, loc } = node
const { slotName, slotProps } = processSlotOutlet(node, context)
// check for <slot name="xxx" OR :name="xxx" />
let nameIndex: number = -1
for (let i = 0; i < props.length; i++) {
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'] = [
context.prefixIdentifiers ? `_ctx.$slots` : `$slots`,
slotName
]
const slotArgs: CallExpression['arguments'] = [$slots, slotName]
const propsWithoutName =
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 (slotProps) {
slotArgs.push(slotProps)
}
if (children.length) {
if (!hasProps) {
if (!slotProps) {
slotArgs.push(`{}`)
}
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
}
}

View File

@ -1,29 +1,23 @@
import { NodeTransform } from '../transform'
import {
NodeTypes,
TemplateChildNode,
TextNode,
InterpolationNode,
CompoundExpressionNode,
createCallExpression,
CallExpression,
ElementTypes
} from '../ast'
import { isText } from '../utils'
import { CREATE_TEXT } from '../runtimeHelpers'
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
// e.g. <div>abc {{ d }} {{ e }}</div> should have a single expression node as child.
export const transformText: NodeTransform = (node, context) => {
if (
node.type === NodeTypes.ROOT ||
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
// been processed.
@ -84,7 +78,7 @@ export const transformText: NodeTransform = (node, context) => {
callArgs.push(child)
}
// 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(
`${PatchFlags.TEXT} /* ${PatchFlagNames[PatchFlags.TEXT]} */`
)

View File

@ -30,7 +30,6 @@ export const transformBind: DirectiveTransform = (dir, node, context) => {
return {
props: [
createObjectProperty(arg!, exp || createSimpleExpression('', true, loc))
],
needRuntime: false
]
}
}

View File

@ -8,22 +8,28 @@ import {
createSimpleExpression,
SourceLocation,
SimpleExpressionNode,
createSequenceExpression,
createCallExpression,
createFunctionExpression,
ElementTypes,
createObjectExpression,
createObjectProperty,
ForCodegenNode,
ElementCodegenNode,
SlotOutletCodegenNode,
SlotOutletNode
RenderSlotCall,
SlotOutletNode,
ElementNode,
DirectiveNode,
ForNode,
PlainElementNode,
createVNodeCall,
VNodeCall,
ForRenderListExpression,
BlockCodegenNode,
ForIteratorExpression
} from '../ast'
import { createCompilerError, ErrorCodes } from '../errors'
import {
getInnerRange,
findProp,
createBlockExpression,
isTemplateNode,
isSlotOutlet,
injectProp
@ -32,8 +38,7 @@ import {
RENDER_LIST,
OPEN_BLOCK,
CREATE_BLOCK,
FRAGMENT,
WITH_DIRECTIVES
FRAGMENT
} from '../runtimeHelpers'
import { processExpression } from './transformExpression'
import { PatchFlags, PatchFlagNames } from '@vue/shared'
@ -41,141 +46,163 @@ import { PatchFlags, PatchFlagNames } from '@vue/shared'
export const transformFor = createStructuralDirectiveTransform(
'for',
(node, dir, context) => {
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 { 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), [
const { helper } = context
return processFor(node, dir, context, forNode => {
// 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), [
forNode.source
]) as ForRenderListExpression
const keyProp = findProp(node, `key`)
const fragmentFlag = keyProp
? PatchFlags.KEYED_FRAGMENT
: PatchFlags.UNKEYED_FRAGMENT
forNode.codegenNode = createVNodeCall(
context,
helper(FRAGMENT),
`null`,
undefined,
renderExp,
`${fragmentFlag} /* ${PatchFlagNames[fragmentFlag]} */`
])
]) as ForCodegenNode
`${fragmentFlag} /* ${PatchFlagNames[fragmentFlag]} */`,
undefined,
undefined,
true /* isBlock */,
true /* isForBlock */,
node.loc
) as ForCodegenNode
context.replaceNode({
type: NodeTypes.FOR,
loc: dir.loc,
source,
valueAlias: value,
keyAlias: key,
objectIndexAlias: index,
children: node.tagType === ElementTypes.TEMPLATE ? node.children : [node],
codegenNode
})
// bookkeeping
scopes.vFor++
if (!__BROWSER__ && context.prefixIdentifiers) {
// scope management
// inject identifiers to context
value && addIdentifiers(value)
key && addIdentifiers(key)
index && addIdentifiers(index)
}
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
return () => {
// finish the codegen now that all children have been traversed
let childBlock: BlockCodegenNode
const isTemplate = isTemplateNode(node)
const { children } = forNode
const needFragmentWrapper =
children.length > 1 || children[0].type !== NodeTypes.ELEMENT
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
const keyProperty = keyProp
? createObjectProperty(
`key`,
keyProp.type === NodeTypes.ATTRIBUTE
? createSimpleExpression(keyProp.value!.content, true)
: keyProp.exp!
)
: null
const keyProperty = keyProp
? createObjectProperty(
`key`,
keyProp.type === NodeTypes.ATTRIBUTE
? createSimpleExpression(keyProp.value!.content, true)
: keyProp.exp!
)
: null
if (slotOutlet) {
// <slot v-for="..."> or <template v-for="..."><slot/></template>
childBlock = slotOutlet.codegenNode as SlotOutletCodegenNode
if (isTemplate && keyProperty) {
// <template v-for="..." :key="..."><slot/></template>
// we need to inject the key to the renderSlot() call.
// 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), [
if (slotOutlet) {
// <slot v-for="..."> or <template v-for="..."><slot/></template>
childBlock = slotOutlet.codegenNode as RenderSlotCall
if (isTemplate && keyProperty) {
// <template v-for="..." :key="..."><slot/></template>
// we need to inject the key to the renderSlot() call.
// the props for renderSlot is passed as the 3rd argument.
injectProp(childBlock, keyProperty, context)
}
} else if (needFragmentWrapper) {
// <template v-for="..."> with text or multi-elements
// should generate a fragment block for each loop
childBlock = createVNodeCall(
context,
helper(FRAGMENT),
keyProperty ? createObjectExpression([keyProperty]) : `null`,
keyProperty ? createObjectExpression([keyProperty]) : undefined,
node.children,
`${PatchFlags.STABLE_FRAGMENT} /* ${
PatchFlagNames[PatchFlags.STABLE_FRAGMENT]
} */`
]),
context
)
} 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)
} */`,
undefined,
undefined,
true
)
} 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(
createFunctionExpression(
createForLoopParams(parseResult),
renderExp.arguments.push(createFunctionExpression(
createForLoopParams(forNode.parseResult),
childBlock,
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]*)/
// 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.

View File

@ -1,7 +1,7 @@
import {
createStructuralDirectiveTransform,
traverseChildren,
TransformContext
TransformContext,
traverseNode
} from '../transform'
import {
NodeTypes,
@ -10,130 +10,139 @@ import {
DirectiveNode,
IfBranchNode,
SimpleExpressionNode,
createSequenceExpression,
createCallExpression,
createConditionalExpression,
ConditionalExpression,
CallExpression,
createSimpleExpression,
createObjectProperty,
createObjectExpression,
IfCodegenNode,
IfConditionalExpression,
BlockCodegenNode,
SlotOutletCodegenNode,
ElementCodegenNode,
ComponentCodegenNode
IfNode,
createVNodeCall
} from '../ast'
import { createCompilerError, ErrorCodes } from '../errors'
import { processExpression } from './transformExpression'
import {
OPEN_BLOCK,
CREATE_BLOCK,
FRAGMENT,
WITH_DIRECTIVES,
CREATE_VNODE,
CREATE_COMMENT
CREATE_COMMENT,
OPEN_BLOCK,
TELEPORT
} from '../runtimeHelpers'
import { injectProp } from '../utils'
import { PatchFlags, PatchFlagNames } from '@vue/shared'
export const transformIf = createStructuralDirectiveTransform(
/^(if|else|else-if)$/,
(node, dir, context) => {
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 codegenNode = createSequenceExpression([
createCallExpression(context.helper(OPEN_BLOCK))
]) as IfCodegenNode
context.replaceNode({
type: NodeTypes.IF,
loc: node.loc,
branches: [branch],
codegenNode
})
return processIf(node, dir, context, (ifNode, branch, isRoot) => {
// Exit callback. Complete the codegenNode when all children have been
// transformed.
return () => {
codegenNode.expressions.push(createCodegenNodeForBranch(
branch,
0,
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
}
}
if (isRoot) {
ifNode.codegenNode = createCodegenNodeForBranch(
branch,
0,
context
) as IfConditionalExpression
} else {
context.onError(
createCompilerError(ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, node.loc)
// attach this branch's codegen node to the v-if root.
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 {
return {
type: NodeTypes.IF_BRANCH,
@ -160,7 +169,7 @@ function createCodegenNodeForBranch(
])
) as IfConditionalExpression
} else {
return createChildrenCodegenNode(branch, index, context) as BlockCodegenNode
return createChildrenCodegenNode(branch, index, context)
}
}
@ -168,46 +177,56 @@ function createChildrenCodegenNode(
branch: IfBranchNode,
index: number,
context: TransformContext
): CallExpression {
): BlockCodegenNode {
const { helper } = context
const keyProperty = createObjectProperty(
`key`,
createSimpleExpression(index + '', false)
)
const { children } = branch
const child = children[0]
const firstChild = children[0]
const needFragmentWrapper =
children.length !== 1 || child.type !== NodeTypes.ELEMENT
children.length !== 1 || firstChild.type !== NodeTypes.ELEMENT
if (needFragmentWrapper) {
const blockArgs: CallExpression['arguments'] = [
helper(FRAGMENT),
createObjectExpression([keyProperty]),
children
]
if (children.length === 1 && child.type === NodeTypes.FOR) {
if (children.length === 1 && firstChild.type === NodeTypes.FOR) {
// optimize away nested fragments when child is a ForNode
const forBlockArgs = child.codegenNode.expressions[1].arguments
// directly use the for block's children and patchFlag
blockArgs[2] = forBlockArgs[2]
blockArgs[3] = forBlockArgs[3]
const vnodeCall = firstChild.codegenNode!
injectProp(vnodeCall, keyProperty, context)
return vnodeCall
} 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 {
const childCodegen = (child as ElementNode).codegenNode as
| ElementCodegenNode
| ComponentCodegenNode
| SlotOutletCodegenNode
let vnodeCall = childCodegen
// Element with custom directives. Locate the actual createVNode() call.
if (vnodeCall.callee === WITH_DIRECTIVES) {
vnodeCall = vnodeCall.arguments[0]
}
const vnodeCall = (firstChild as ElementNode)
.codegenNode as BlockCodegenNode
// Change createVNode to createBlock.
if (vnodeCall.callee === CREATE_VNODE) {
vnodeCall.callee = helper(CREATE_BLOCK)
if (
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
injectProp(vnodeCall, keyProperty, context)
return childCodegen
return vnodeCall
}
}

View File

@ -44,10 +44,7 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
const eventName = arg
? arg.type === NodeTypes.SIMPLE_EXPRESSION && arg.isStatic
? `onUpdate:${arg.content}`
: createCompoundExpression([
'"onUpdate:" + ',
...(arg.type === NodeTypes.SIMPLE_EXPRESSION ? [arg] : arg.children)
])
: createCompoundExpression(['"onUpdate:" + ', arg])
: `onUpdate:modelValue`
const props = [
@ -56,11 +53,7 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
// "onUpdate:modelValue": $event => (foo = $event)
createObjectProperty(
eventName,
createCompoundExpression([
`$event => (`,
...(exp.type === NodeTypes.SIMPLE_EXPRESSION ? [exp] : exp.children),
` = $event)`
])
createCompoundExpression([`$event => (`, exp, ` = $event)`])
)
]
@ -82,12 +75,7 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
const modifiersKey = arg
? arg.type === NodeTypes.SIMPLE_EXPRESSION && arg.isStatic
? `${arg.content}Modifiers`
: createCompoundExpression([
...(arg.type === NodeTypes.SIMPLE_EXPRESSION
? [arg]
: arg.children),
' + "Modifiers"'
])
: createCompoundExpression([arg, ' + "Modifiers"'])
: `modelModifiers`
props.push(
createObjectProperty(
@ -101,5 +89,5 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
}
function createTransformProps(props: Property[] = []) {
return { props, needRuntime: false }
return { props }
}

View File

@ -8,7 +8,7 @@ import {
createCompoundExpression,
SimpleExpressionNode
} from '../ast'
import { capitalize } from '@vue/shared'
import { capitalize, camelize } from '@vue/shared'
import { createCompilerError, ErrorCodes } from '../errors'
import { processExpression } from './transformExpression'
import { isMemberExpression, hasScopeRef } from '../utils'
@ -26,23 +26,24 @@ export interface VOnDirectiveNode extends DirectiveNode {
}
export const transformOn: DirectiveTransform = (
dir: VOnDirectiveNode,
dir,
node,
context,
augmentor
) => {
const { loc, modifiers, arg } = dir
const { loc, modifiers, arg } = dir as VOnDirectiveNode
if (!dir.exp && !modifiers.length) {
context.onError(createCompilerError(ErrorCodes.X_V_ON_NO_EXPRESSION, loc))
}
let eventName: ExpressionNode
if (arg.type === NodeTypes.SIMPLE_EXPRESSION) {
if (arg.isStatic) {
eventName = createSimpleExpression(
`on${capitalize(arg.content)}`,
true,
arg.loc
)
const rawName = arg.content
// for @vnode-xxx event listeners, auto convert it to camelCase
const normalizedName = rawName.startsWith(`vnode`)
? capitalize(camelize(rawName))
: capitalize(rawName)
eventName = createSimpleExpression(`on${normalizedName}`, true, arg.loc)
} else {
eventName = createCompoundExpression([`"on" + (`, arg, `)`])
}
@ -54,16 +55,22 @@ export const transformOn: DirectiveTransform = (
}
// 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
if (exp) {
const isMemberExp = isMemberExpression(exp.content)
const isInlineStatement = !(isMemberExp || fnExpRE.test(exp.content))
const hasMultipleStatements = exp.content.includes(`;`)
// process the expression since it's been skipped
if (!__BROWSER__ && context.prefixIdentifiers) {
context.addIdentifiers(`$event`)
exp = processExpression(exp, context)
exp = processExpression(exp, context, false, hasMultipleStatements)
context.removeIdentifiers(`$event`)
// with scope analysis, the function is hoistable if it has no reference
// to scope variables.
@ -85,9 +92,9 @@ export const transformOn: DirectiveTransform = (
if (isInlineStatement || (isCacheable && isMemberExp)) {
// wrap inline statement in a function expression
exp = createCompoundExpression([
`$event => (`,
...(exp.type === NodeTypes.SIMPLE_EXPRESSION ? [exp] : exp.children),
`)`
`$event => ${hasMultipleStatements ? `{` : `(`}`,
exp,
hasMultipleStatements ? `}` : `)`
])
}
}
@ -98,8 +105,7 @@ export const transformOn: DirectiveTransform = (
eventName,
exp || createSimpleExpression(`() => {}`, false, loc)
)
],
needRuntime: false
]
}
// apply extended compiler augmentor

View File

@ -19,12 +19,13 @@ import {
FunctionExpression,
CallExpression,
createCallExpression,
createArrayExpression
createArrayExpression,
SlotsExpression
} from '../ast'
import { TransformContext, NodeTransform } from '../transform'
import { createCompilerError, ErrorCodes } from '../errors'
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'
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
// transformElement to build the slots object for a component.
export function buildSlots(
node: ElementNode,
context: TransformContext
context: TransformContext,
buildSlotFn: SlotFnBuilder = buildClientSlotFn
): {
slots: ObjectExpression | CallExpression
slots: SlotsExpression
hasDynamicSlots: boolean
} {
context.helper(WITH_CTX)
const { children, loc } = node
const slotsProperties: Property[] = []
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
// since it likely uses a scope variable.
let hasDynamicSlots = context.scopes.vSlot > 0 || context.scopes.vFor > 0
@ -115,24 +139,26 @@ export function buildSlots(
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 }"/>
const explicitDefaultSlot = findDir(node, 'slot', true)
if (explicitDefaultSlot) {
const { arg, exp, loc } = explicitDefaultSlot
if (arg) {
context.onError(
createCompilerError(ErrorCodes.X_V_SLOT_NAMED_SLOT_ON_COMPONENT, loc)
const onComponentSlot = findDir(node, 'slot', true)
if (onComponentSlot) {
const { arg, exp } = onComponentSlot
slotsProperties.push(
createObjectProperty(
arg || createSimpleExpression('default', true),
buildSlotFn(exp, children, loc)
)
}
slotsProperties.push(buildDefaultSlot(exp, children, loc))
)
}
// 2. Iterate through children and check for template slots
// <template v-slot:foo="{ prop }">
let hasTemplateSlots = false
let extraneousChild: TemplateChildNode | undefined = undefined
let hasNamedDefaultSlot = false
const implicitDefaultChildren: TemplateChildNode[] = []
const seenSlotNames = new Set<string>()
for (let i = 0; i < children.length; i++) {
const slotElement = children[i]
let slotDir
@ -142,14 +168,14 @@ export function buildSlots(
!(slotDir = findDir(slotElement, 'slot', true))
) {
// not a <template v-slot>, skip.
if (slotElement.type !== NodeTypes.COMMENT && !extraneousChild) {
extraneousChild = slotElement
if (slotElement.type !== NodeTypes.COMMENT) {
implicitDefaultChildren.push(slotElement)
}
continue
}
if (explicitDefaultSlot) {
// already has on-component default slot - this is incorrect usage.
if (onComponentSlot) {
// already has on-component slot - this is incorrect usage.
context.onError(
createCompilerError(ErrorCodes.X_V_SLOT_MIXED_SLOT_USAGE, slotDir.loc)
)
@ -172,13 +198,7 @@ export function buildSlots(
hasDynamicSlots = true
}
const slotFunction = createFunctionExpression(
slotProps,
slotChildren,
false,
slotChildren.length ? slotChildren[0].loc : slotLoc
)
const slotFunction = buildSlotFn(slotProps, slotChildren, slotLoc)
// check if this slot is conditional (v-if/v-for)
let vIf: DirectiveNode | undefined
let vElse: DirectiveNode | undefined
@ -244,7 +264,7 @@ export function buildSlots(
createFunctionExpression(
createForLoopParams(parseResult),
buildDynamicSlot(slotName, slotFunction),
true
true /* force newline */
)
])
)
@ -266,36 +286,46 @@ export function buildSlots(
continue
}
seenSlotNames.add(staticSlotName)
if (staticSlotName === 'default') {
hasNamedDefaultSlot = true
}
}
slotsProperties.push(createObjectProperty(slotName, slotFunction))
}
}
if (hasTemplateSlots && extraneousChild) {
context.onError(
createCompilerError(
ErrorCodes.X_V_SLOT_EXTRANEOUS_NON_SLOT_CHILDREN,
extraneousChild.loc
)
)
if (!onComponentSlot) {
if (!hasTemplateSlots) {
// implicit default slot (on component)
slotsProperties.push(buildDefaultSlotProperty(undefined, children))
} 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) {
// implicit default slot.
slotsProperties.push(buildDefaultSlot(undefined, children, loc))
}
let slots: ObjectExpression | CallExpression = createObjectExpression(
let slots = createObjectExpression(
slotsProperties.concat(
createObjectProperty(`_compiled`, createSimpleExpression(`true`, false))
createObjectProperty(`_`, createSimpleExpression(`1`, false))
),
loc
)
) as SlotsExpression
if (dynamicSlots.length) {
slots = createCallExpression(context.helper(CREATE_SLOTS), [
slots,
createArrayExpression(dynamicSlots)
])
]) as SlotsExpression
}
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(
name: ExpressionNode,
fn: FunctionExpression

View File

@ -4,8 +4,6 @@ import {
ElementNode,
NodeTypes,
CallExpression,
SequenceExpression,
createSequenceExpression,
createCallExpression,
DirectiveNode,
ElementTypes,
@ -17,33 +15,31 @@ import {
createObjectExpression,
SlotOutletNode,
TemplateNode,
BlockCodegenNode,
ElementCodegenNode,
SlotOutletCodegenNode,
ComponentCodegenNode,
RenderSlotCall,
ExpressionNode,
IfBranchNode
IfBranchNode,
TextNode,
InterpolationNode,
VNodeCall
} from './ast'
import { parse } from 'acorn'
import { walk } from 'estree-walker'
import { TransformContext } from './transform'
import {
OPEN_BLOCK,
MERGE_PROPS,
RENDER_SLOT,
PORTAL,
TELEPORT,
SUSPENSE,
KEEP_ALIVE,
BASE_TRANSITION
} from './runtimeHelpers'
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 =>
tag === expected || tag === hyphenate(expected)
export function isCoreComponent(tag: string): symbol | void {
if (isBuiltInType(tag, 'Portal')) {
return PORTAL
if (isBuiltInType(tag, 'Teleport')) {
return TELEPORT
} else if (isBuiltInType(tag, 'Suspense')) {
return SUSPENSE
} 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
// and thus can be tree-shaken in browser builds.
let _parse: typeof parse
let _walk: typeof walk
let _walk: any
export function loadDep(name: string) {
if (typeof process !== 'undefined' && isFunction(require)) {
if (!__BROWSER__ && typeof process !== 'undefined' && isFunction(require)) {
return require(name)
} else {
// 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__,
`Expression AST analysis can only be performed in non-browser builds.`
)
const parse = _parse || (_parse = loadDep('acorn').parse)
return parse(code, options)
if (!_parse) {
_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(
!__BROWSER__,
`Expression AST analysis can only be performed in non-browser builds.`
@ -149,7 +152,7 @@ export function advancePositionWithMutation(
pos.column =
lastNewLinePos === -1
? pos.column + numberOfCharacters
: Math.max(1, numberOfCharacters - lastNewLinePos)
: numberOfCharacters - lastNewLinePos
return pos
}
@ -181,36 +184,46 @@ export function findDir(
export function findProp(
node: ElementNode,
name: string,
dynamicOnly: boolean = false
dynamicOnly: boolean = false,
allowEmpty: boolean = false
): ElementNode['props'][0] | undefined {
for (let i = 0; i < node.props.length; i++) {
const p = node.props[i]
if (p.type === NodeTypes.ATTRIBUTE) {
if (dynamicOnly) continue
if (p.name === name && p.value) {
if (p.name === name && (p.value || allowEmpty)) {
return p
}
} else if (
p.name === 'bind' &&
p.arg &&
p.arg.type === NodeTypes.SIMPLE_EXPRESSION &&
p.arg.isStatic &&
p.arg.content === name &&
p.exp
) {
} else if (p.name === 'bind' && p.exp && isBindKey(p.arg, name)) {
return p
}
}
}
export function createBlockExpression(
blockExp: BlockCodegenNode,
context: TransformContext
): SequenceExpression {
return createSequenceExpression([
createCallExpression(context.helper(OPEN_BLOCK)),
blockExp
])
export function isBindKey(arg: DirectiveNode['arg'], name: string): boolean {
return !!(
arg &&
arg.type === NodeTypes.SIMPLE_EXPRESSION &&
arg.isStatic &&
arg.content === name
)
}
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 {
@ -232,13 +245,13 @@ export function isSlotOutlet(
}
export function injectProp(
node: ElementCodegenNode | ComponentCodegenNode | SlotOutletCodegenNode,
node: VNodeCall | RenderSlotCall,
prop: Property,
context: TransformContext
) {
let propsWithInjection: ObjectExpression | CallExpression
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)) {
propsWithInjection = createObjectExpression([prop])
} else if (props.type === NodeTypes.JS_CALL_EXPRESSION) {
@ -253,7 +266,19 @@ export function injectProp(
}
propsWithInjection = props
} 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
} else {
// single v-bind with expression, return a merged replacement
@ -262,10 +287,10 @@ export function injectProp(
props
])
}
if (node.callee === RENDER_SLOT) {
node.arguments[2] = propsWithInjection
if (node.type === NodeTypes.VNODE_CALL) {
node.props = propsWithInjection
} else {
node.arguments[1] = propsWithInjection
node.arguments[2] = propsWithInjection
}
}

View File

@ -2,19 +2,16 @@
exports[`compile should contain standard transforms 1`] = `
"const _Vue = Vue
const { createVNode: _createVNode } = Vue
const _hoisted_1 = {}
return function render(_ctx, _cache) {
with (_ctx) {
const { createVNode: _createVNode, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return function render() {
with (this) {
const { createVNode: _createVNode, createBlock: _createBlock, Fragment: _Fragment, openBlock: _openBlock } = _Vue
return (_openBlock(), _createBlock(_Fragment, null, [
_createVNode(\\"div\\", { textContent: text }, null, 8 /* PROPS */, [\\"textContent\\"]),
_createVNode(\\"div\\", { innerHTML: html }, null, 8 /* PROPS */, [\\"innerHTML\\"]),
_createVNode(\\"div\\", null, \\"test\\"),
_createVNode(\\"div\\", { style: _hoisted_1 }, \\"red\\"),
_createVNode(\\"div\\", { style: {\\"color\\":\\"red\\"} }, \\"red\\"),
_createVNode(\\"div\\", { style: {color: 'green'} }, null, 4 /* STYLE */)
], 64 /* STABLE_FRAGMENT */))
}

View File

@ -5,7 +5,7 @@ describe('compile', () => {
const { code } = compile(`<div v-text="text"></div>
<div v-html="html"></div>
<div v-cloak>test</div>
<div style="color=red">red</div>
<div style="color:red">red</div>
<div :style="{color: 'green'}"></div>`)
expect(code).toMatchSnapshot()

View File

@ -1,5 +1,5 @@
import {
parse,
baseParse as parse,
NodeTypes,
ElementNode,
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', () => {
const ast = parse(
'<style>some<div>text</div>and<!--comment--></style>',
@ -100,11 +116,36 @@ describe('DOM parser', () => {
})
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)
expect((ast.children[0] as ElementNode).children[0]).toMatchObject({
type: NodeTypes.TEXT,
content: rawText
content: rawText.slice(1)
})
})
})

View File

@ -1,23 +1,57 @@
// 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`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { vModelText: _vModelText, createVNode: _createVNode, withDirectives: _withDirectives, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return (_openBlock(), _withDirectives(_createBlock(\\"input\\", {
modelValue: model,
return function render(_ctx, _cache) {
with (_ctx) {
const { vModelText: _vModelText, createVNode: _createVNode, withDirectives: _withDirectives, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return _withDirectives((_openBlock(), _createBlock(\\"input\\", {
\\"onUpdate:modelValue\\": $event => (model = $event)
}, null, 8 /* PROPS */, [\\"modelValue\\", \\"onUpdate:modelValue\\"]), [
}, null, 8 /* PROPS */, [\\"onUpdate:modelValue\\"])), [
[
_vModelText,
model,
void 0,
{ lazy: true }
]
]))
])
}
}"
`;
@ -25,21 +59,20 @@ return function render() {
exports[`compiler: transform v-model modifiers .number 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { vModelText: _vModelText, createVNode: _createVNode, withDirectives: _withDirectives, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return (_openBlock(), _withDirectives(_createBlock(\\"input\\", {
modelValue: model,
return function render(_ctx, _cache) {
with (_ctx) {
const { vModelText: _vModelText, createVNode: _createVNode, withDirectives: _withDirectives, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return _withDirectives((_openBlock(), _createBlock(\\"input\\", {
\\"onUpdate:modelValue\\": $event => (model = $event)
}, null, 8 /* PROPS */, [\\"modelValue\\", \\"onUpdate:modelValue\\"]), [
}, null, 8 /* PROPS */, [\\"onUpdate:modelValue\\"])), [
[
_vModelText,
model,
void 0,
{ number: true }
]
]))
])
}
}"
`;
@ -47,21 +80,20 @@ return function render() {
exports[`compiler: transform v-model modifiers .trim 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { vModelText: _vModelText, createVNode: _createVNode, withDirectives: _withDirectives, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return (_openBlock(), _withDirectives(_createBlock(\\"input\\", {
modelValue: model,
return function render(_ctx, _cache) {
with (_ctx) {
const { vModelText: _vModelText, createVNode: _createVNode, withDirectives: _withDirectives, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return _withDirectives((_openBlock(), _createBlock(\\"input\\", {
\\"onUpdate:modelValue\\": $event => (model = $event)
}, null, 8 /* PROPS */, [\\"modelValue\\", \\"onUpdate:modelValue\\"]), [
}, null, 8 /* PROPS */, [\\"onUpdate:modelValue\\"])), [
[
_vModelText,
model,
void 0,
{ trim: true }
]
]))
])
}
}"
`;
@ -69,16 +101,15 @@ return function render() {
exports[`compiler: transform v-model simple expression 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { vModelText: _vModelText, createVNode: _createVNode, withDirectives: _withDirectives, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return (_openBlock(), _withDirectives(_createBlock(\\"input\\", {
modelValue: model,
return function render(_ctx, _cache) {
with (_ctx) {
const { vModelText: _vModelText, createVNode: _createVNode, withDirectives: _withDirectives, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return _withDirectives((_openBlock(), _createBlock(\\"input\\", {
\\"onUpdate:modelValue\\": $event => (model = $event)
}, null, 8 /* PROPS */, [\\"modelValue\\", \\"onUpdate:modelValue\\"]), [
}, null, 8 /* PROPS */, [\\"onUpdate:modelValue\\"])), [
[_vModelText, model]
]))
])
}
}"
`;
@ -86,17 +117,16 @@ return function render() {
exports[`compiler: transform v-model simple expression for input (checkbox) 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { vModelCheckbox: _vModelCheckbox, createVNode: _createVNode, withDirectives: _withDirectives, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return (_openBlock(), _withDirectives(_createBlock(\\"input\\", {
return function render(_ctx, _cache) {
with (_ctx) {
const { vModelCheckbox: _vModelCheckbox, createVNode: _createVNode, withDirectives: _withDirectives, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return _withDirectives((_openBlock(), _createBlock(\\"input\\", {
type: \\"checkbox\\",
modelValue: model,
\\"onUpdate:modelValue\\": $event => (model = $event)
}, null, 8 /* PROPS */, [\\"modelValue\\", \\"onUpdate:modelValue\\"]), [
}, null, 8 /* PROPS */, [\\"onUpdate:modelValue\\"])), [
[_vModelCheckbox, model]
]))
])
}
}"
`;
@ -104,19 +134,18 @@ return function render() {
exports[`compiler: transform v-model simple expression for input (dynamic type) 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { vModelDynamic: _vModelDynamic, createVNode: _createVNode, withDirectives: _withDirectives, resolveDirective: _resolveDirective, createBlock: _createBlock, openBlock: _openBlock } = _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 (_openBlock(), _withDirectives(_createBlock(\\"input\\", {
modelValue: model,
return _withDirectives((_openBlock(), _createBlock(\\"input\\", {
\\"onUpdate:modelValue\\": $event => (model = $event)
}, null, 8 /* PROPS */, [\\"modelValue\\", \\"onUpdate:modelValue\\"]), [
}, null, 8 /* PROPS */, [\\"onUpdate:modelValue\\"])), [
[_directive_bind, foo, \\"type\\"],
[_vModelDynamic, model]
]))
])
}
}"
`;
@ -124,17 +153,16 @@ return function render() {
exports[`compiler: transform v-model simple expression for input (radio) 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { vModelRadio: _vModelRadio, createVNode: _createVNode, withDirectives: _withDirectives, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return (_openBlock(), _withDirectives(_createBlock(\\"input\\", {
return function render(_ctx, _cache) {
with (_ctx) {
const { vModelRadio: _vModelRadio, createVNode: _createVNode, withDirectives: _withDirectives, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return _withDirectives((_openBlock(), _createBlock(\\"input\\", {
type: \\"radio\\",
modelValue: model,
\\"onUpdate:modelValue\\": $event => (model = $event)
}, null, 8 /* PROPS */, [\\"modelValue\\", \\"onUpdate:modelValue\\"]), [
}, null, 8 /* PROPS */, [\\"onUpdate:modelValue\\"])), [
[_vModelRadio, model]
]))
])
}
}"
`;
@ -142,17 +170,16 @@ return function render() {
exports[`compiler: transform v-model simple expression for input (text) 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { vModelText: _vModelText, createVNode: _createVNode, withDirectives: _withDirectives, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return (_openBlock(), _withDirectives(_createBlock(\\"input\\", {
return function render(_ctx, _cache) {
with (_ctx) {
const { vModelText: _vModelText, createVNode: _createVNode, withDirectives: _withDirectives, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return _withDirectives((_openBlock(), _createBlock(\\"input\\", {
type: \\"text\\",
modelValue: model,
\\"onUpdate:modelValue\\": $event => (model = $event)
}, null, 8 /* PROPS */, [\\"modelValue\\", \\"onUpdate:modelValue\\"]), [
}, null, 8 /* PROPS */, [\\"onUpdate:modelValue\\"])), [
[_vModelText, model]
]))
])
}
}"
`;
@ -160,16 +187,15 @@ return function render() {
exports[`compiler: transform v-model simple expression for select 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { vModelSelect: _vModelSelect, createVNode: _createVNode, withDirectives: _withDirectives, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return (_openBlock(), _withDirectives(_createBlock(\\"select\\", {
modelValue: model,
return function render(_ctx, _cache) {
with (_ctx) {
const { vModelSelect: _vModelSelect, createVNode: _createVNode, withDirectives: _withDirectives, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return _withDirectives((_openBlock(), _createBlock(\\"select\\", {
\\"onUpdate:modelValue\\": $event => (model = $event)
}, null, 8 /* PROPS */, [\\"modelValue\\", \\"onUpdate:modelValue\\"]), [
}, null, 8 /* PROPS */, [\\"onUpdate:modelValue\\"])), [
[_vModelSelect, model]
]))
])
}
}"
`;
@ -177,16 +203,15 @@ return function render() {
exports[`compiler: transform v-model simple expression for textarea 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { vModelText: _vModelText, createVNode: _createVNode, withDirectives: _withDirectives, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return (_openBlock(), _withDirectives(_createBlock(\\"textarea\\", {
modelValue: model,
return function render(_ctx, _cache) {
with (_ctx) {
const { vModelText: _vModelText, createVNode: _createVNode, withDirectives: _withDirectives, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return _withDirectives((_openBlock(), _createBlock(\\"textarea\\", {
\\"onUpdate:modelValue\\": $event => (model = $event)
}, null, 8 /* PROPS */, [\\"modelValue\\", \\"onUpdate:modelValue\\"]), [
}, null, 8 /* PROPS */, [\\"onUpdate:modelValue\\"])), [
[_vModelText, model]
]))
])
}
}"
`;

View File

@ -3,13 +3,13 @@
exports[`compiler: v-show transform simple expression 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { vShow: _vShow, createVNode: _createVNode, withDirectives: _withDirectives, createBlock: _createBlock, openBlock: _openBlock } = _Vue
return (_openBlock(), _withDirectives(_createBlock(\\"div\\", null, null, 32 /* NEED_PATCH */), [
return function render(_ctx, _cache) {
with (_ctx) {
const { vShow: _vShow, createVNode: _createVNode, withDirectives: _withDirectives, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return _withDirectives((_openBlock(), _createBlock(\\"div\\", null, null, 512 /* NEED_PATCH */)), [
[_vShow, a]
]))
])
}
}"
`;

View File

@ -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' + '&gt;ar'">{{ 1 }} + {{ '<' }}</span>` +
`<span>&amp;</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&gt;ar">1 + &lt;</span>` + `<span>&amp;</span>`,
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
)}</div>`
)
]
})
})
})

View File

@ -1,10 +1,10 @@
import {
parse,
baseParse as parse,
transform,
CompilerOptions,
ElementNode,
NodeTypes,
CallExpression
VNodeCall
} from '@vue/compiler-core'
import { transformBind } from '../../../compiler-core/src/transforms/vBind'
import { transformElement } from '../../../compiler-core/src/transforms/transformElement'
@ -26,17 +26,8 @@ function transformWithStyleTransform(
}
describe('compiler: style transform', () => {
test('should transform into directive node and hoist value', () => {
const { root, node } = transformWithStyleTransform(
`<div style="color: red"/>`
)
expect(root.hoists).toMatchObject([
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: `{"color":"red"}`,
isStatic: false
}
])
test('should transform into directive node', () => {
const { node } = transformWithStyleTransform(`<div style="color: red"/>`)
expect(node.props[0]).toMatchObject({
type: NodeTypes.DIRECTIVE,
name: `bind`,
@ -47,7 +38,7 @@ describe('compiler: style transform', () => {
},
exp: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `_hoisted_1`,
content: `{"color":"red"}`,
isStatic: false
}
})
@ -60,7 +51,7 @@ describe('compiler: style transform', () => {
bind: transformBind
}
})
expect((node.codegenNode as CallExpression).arguments[1]).toMatchObject({
expect((node.codegenNode as VNodeCall).props).toMatchObject({
type: NodeTypes.JS_OBJECT_EXPRESSION,
properties: [
{
@ -71,13 +62,13 @@ describe('compiler: style transform', () => {
},
value: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `_hoisted_1`,
content: `{"color":"red"}`,
isStatic: false
}
}
]
})
// 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()
})
})

View File

@ -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)
})
})

View File

@ -1,5 +1,5 @@
import {
parse,
baseParse as parse,
transform,
PlainElementNode,
CompilerOptions
@ -29,15 +29,13 @@ describe('compiler: v-html transform', () => {
it('should convert v-html to innerHTML', () => {
const ast = transformWithVHtml(`<div v-html="test"/>`)
expect((ast.children[0] as PlainElementNode).codegenNode).toMatchObject({
arguments: [
`"div"`,
createObjectMatcher({
innerHTML: `[test]`
}),
`null`,
genFlagText(PatchFlags.PROPS),
`["innerHTML"]`
]
tag: `"div"`,
props: createObjectMatcher({
innerHTML: `[test]`
}),
children: undefined,
patchFlag: genFlagText(PatchFlags.PROPS),
dynamicProps: `["innerHTML"]`
})
})
@ -50,15 +48,13 @@ describe('compiler: v-html transform', () => {
[{ code: DOMErrorCodes.X_V_HTML_WITH_CHILDREN }]
])
expect((ast.children[0] as PlainElementNode).codegenNode).toMatchObject({
arguments: [
`"div"`,
createObjectMatcher({
innerHTML: `[test]`
}),
`null`, // <-- children should have been removed
genFlagText(PatchFlags.PROPS),
`["innerHTML"]`
]
tag: `"div"`,
props: createObjectMatcher({
innerHTML: `[test]`
}),
children: undefined, // <-- children should have been removed
patchFlag: genFlagText(PatchFlags.PROPS),
dynamicProps: `["innerHTML"]`
})
})

View File

@ -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 { transformElement } from '../../../compiler-core/src/transforms/transformElement'
import { DOMErrorCodes } from '../../src/errors'
@ -58,6 +63,19 @@ describe('compiler: transform v-model', () => {
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', () => {
const root = transformWithModel('<select v-model="model" />')

View File

@ -1,17 +1,21 @@
import {
parse,
baseParse as parse,
transform,
CompilerOptions,
ElementNode,
ObjectExpression,
CallExpression,
NodeTypes
NodeTypes,
VNodeCall
} from '@vue/compiler-core'
import { transformOn } from '../../src/transforms/vOn'
import { V_ON_WITH_MODIFIERS, V_ON_WITH_KEYS } from '../../src/runtimeHelpers'
import { transformElement } from '../../../compiler-core/src/transforms/transformElement'
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 = {}) {
const ast = parse(template)
@ -24,8 +28,8 @@ function parseWithVOn(template: string, options: CompilerOptions = {}) {
})
return {
root: ast,
props: (((ast.children[0] as ElementNode).codegenNode as CallExpression)
.arguments[1] as ObjectExpression).properties
props: (((ast.children[0] as ElementNode).codegenNode as VNodeCall)
.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', () => {
const {
root,
@ -157,8 +213,11 @@ describe('compiler-dom: transform v-on', () => {
cacheHandlers: true
})
expect(root.cached).toBe(1)
// should not treat cached handler as dynamicProp, so no flags
expect((root as any).children[0].codegenNode.arguments.length).toBe(2)
// should not treat cached handler as dynamicProp, so it should have no
// dynamicProps flags and only the hydration flag
expect((root as any).children[0].codegenNode.patchFlag).toBe(
genFlagText(PatchFlags.HYDRATE_EVENTS)
)
expect(prop.value).toMatchObject({
type: NodeTypes.JS_CACHE_EXPRESSION,
index: 1,

View File

@ -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 { transformShow } from '../../src/transforms/vShow'
import { DOMErrorCodes } from '../../src/errors'

View File

@ -1,5 +1,5 @@
import {
parse,
baseParse as parse,
transform,
PlainElementNode,
CompilerOptions
@ -29,15 +29,13 @@ describe('compiler: v-text transform', () => {
it('should convert v-text to textContent', () => {
const ast = transformWithVText(`<div v-text="test"/>`)
expect((ast.children[0] as PlainElementNode).codegenNode).toMatchObject({
arguments: [
`"div"`,
createObjectMatcher({
textContent: `[test]`
}),
`null`,
genFlagText(PatchFlags.PROPS),
`["textContent"]`
]
tag: `"div"`,
props: createObjectMatcher({
textContent: `[test]`
}),
children: undefined,
patchFlag: genFlagText(PatchFlags.PROPS),
dynamicProps: `["textContent"]`
})
})
@ -50,15 +48,13 @@ describe('compiler: v-text transform', () => {
[{ code: DOMErrorCodes.X_V_TEXT_WITH_CHILDREN }]
])
expect((ast.children[0] as PlainElementNode).codegenNode).toMatchObject({
arguments: [
`"div"`,
createObjectMatcher({
textContent: `[test]`
}),
`null`, // <-- children should have been removed
genFlagText(PatchFlags.PROPS),
`["textContent"]`
]
tag: `"div"`,
props: createObjectMatcher({
textContent: `[test]`
}),
children: undefined, // <-- children should have been removed
patchFlag: genFlagText(PatchFlags.PROPS),
dynamicProps: `["textContent"]`
})
})

View File

@ -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
)
})
})
})

View File

@ -1,18 +1,18 @@
{
"name": "@vue/compiler-dom",
"version": "3.0.0-alpha.0",
"version": "3.0.0-alpha.11",
"description": "@vue/compiler-dom",
"main": "index.js",
"module": "dist/compiler-dom.esm-bundler.js",
"types": "dist/compiler-dom.d.ts",
"unpkg": "dist/compiler-dom/global.js",
"files": [
"index.js",
"dist"
],
"types": "dist/compiler-dom.d.ts",
"unpkg": "dist/compiler-dom/global.js",
"sideEffects": false,
"buildOptions": {
"name": "VueDOMCompiler",
"name": "VueCompilerDOM",
"formats": [
"esm-bundler",
"cjs",
@ -22,7 +22,7 @@
},
"repository": {
"type": "git",
"url": "git+https://github.com/vuejs/vue.git"
"url": "git+https://github.com/vuejs/vue-next.git"
},
"keywords": [
"vue"
@ -30,10 +30,11 @@
"author": "Evan You",
"license": "MIT",
"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": {
"@vue/compiler-core": "3.0.0-alpha.0"
"@vue/shared": "3.0.0-alpha.11",
"@vue/compiler-core": "3.0.0-alpha.11"
}
}

View File

@ -28,7 +28,10 @@ export const enum DOMErrorCodes {
X_V_MODEL_ON_INVALID_ELEMENT,
X_V_MODEL_ARG_ON_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 } = {
@ -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_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_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.`
}

View File

@ -1,45 +1,67 @@
import {
baseCompile,
baseParse,
CompilerOptions,
CodegenResult,
isBuiltInType
ParserOptions,
RootNode,
noopDirectiveTransform,
NodeTransform,
DirectiveTransform
} from '@vue/compiler-core'
import { parserOptionsMinimal } from './parserOptionsMinimal'
import { parserOptionsStandard } from './parserOptionsStandard'
import { transformStyle } from './transforms/transformStyle'
import { transformCloak } from './transforms/vCloak'
import { transformVHtml } from './transforms/vHtml'
import { transformVText } from './transforms/vText'
import { transformModel } from './transforms/vModel'
import { transformOn } from './transforms/vOn'
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(
template: string,
options: CompilerOptions = {}
): CodegenResult {
return baseCompile(template, {
...parserOptions,
...options,
...(__BROWSER__ ? parserOptionsMinimal : parserOptionsStandard),
nodeTransforms: [transformStyle, ...(options.nodeTransforms || [])],
nodeTransforms: [...DOMNodeTransforms, ...(options.nodeTransforms || [])],
directiveTransforms: {
cloak: transformCloak,
html: transformVHtml,
text: transformVText,
model: transformModel, // override compiler-core
on: transformOn,
show: transformShow,
...DOMDirectiveTransforms,
...(options.directiveTransforms || {})
},
isBuiltInComponent: tag => {
if (isBuiltInType(tag, `Transition`)) {
return TRANSITION
} else if (isBuiltInType(tag, `TransitionGroup`)) {
return TRANSITION_GROUP
}
}
transformHoist: __BROWSER__ ? null : stringifyStatic
})
}
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'

View File

@ -3,9 +3,11 @@ import {
ParserOptions,
ElementNode,
Namespaces,
NodeTypes
NodeTypes,
isBuiltInType
} from '@vue/compiler-core'
import { makeMap, isVoidTag, isHTMLTag, isSVGTag } from '@vue/shared'
import { TRANSITION, TRANSITION_GROUP } from './runtimeHelpers'
const isRawTextContainer = /*#__PURE__*/ makeMap(
'style,iframe,script,noscript',
@ -23,6 +25,14 @@ export const parserOptionsMinimal: ParserOptions = {
isNativeTag: tag => isHTMLTag(tag) || isSVGTag(tag),
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
getNamespace(tag: string, parent: ElementNode | undefined): DOMNamespaces {
let ns = parent ? parent.ns : DOMNamespaces.HTML

View 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
}
}

View File

@ -1,7 +1,9 @@
import {
NodeTransform,
NodeTypes,
createSimpleExpression
createSimpleExpression,
SimpleExpressionNode,
SourceLocation
} from '@vue/compiler-core'
// 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) => {
if (p.type === NodeTypes.ATTRIBUTE && p.name === 'style' && p.value) {
// 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] = {
type: NodeTypes.DIRECTIVE,
name: `bind`,
arg: createSimpleExpression(`style`, true, p.loc),
exp,
exp: parseInlineCSS(p.value.content, p.loc),
modifiers: [],
loc: p.loc
}
@ -33,7 +33,10 @@ export const transformStyle: NodeTransform = (node, context) => {
const listDelimiterRE = /;(?![^(]*\))/g
const propertyDelimiterRE = /:(.+)/
function parseInlineCSS(cssText: string): Record<string, string> {
function parseInlineCSS(
cssText: string,
loc: SourceLocation
): SimpleExpressionNode {
const res: Record<string, string> = {}
cssText.split(listDelimiterRE).forEach(item => {
if (item) {
@ -41,5 +44,5 @@ function parseInlineCSS(cssText: string): Record<string, string> {
tmp.length > 1 && (res[tmp[0].trim()] = tmp[1].trim())
}
})
return res
return createSimpleExpression(JSON.stringify(res), false, loc, true)
}

View File

@ -1,5 +0,0 @@
import { DirectiveTransform } from 'packages/compiler-core/src/transform'
export const transformCloak: DirectiveTransform = (node, context) => {
return { props: [], needRuntime: false }
}

View File

@ -24,7 +24,6 @@ export const transformVHtml: DirectiveTransform = (dir, node, context) => {
createSimpleExpression(`innerHTML`, true, loc),
exp || createSimpleExpression('', true)
)
],
needRuntime: false
]
}
}

View File

@ -3,7 +3,8 @@ import {
DirectiveTransform,
ElementTypes,
findProp,
NodeTypes
NodeTypes,
hasDynamicKeyVBind
} from '@vue/compiler-core'
import { createDOMCompilerError, DOMErrorCodes } from '../errors'
import {
@ -16,68 +17,104 @@ import {
export const transformModel: DirectiveTransform = (dir, node, context) => {
const baseResult = baseTransform(dir, node, context)
// base transform has errors
if (!baseResult.props.length) {
// base transform has errors OR component v-model (only need props)
if (!baseResult.props.length || node.tagType === ElementTypes.COMPONENT) {
return baseResult
}
const { tag, tagType } = node
if (tagType === ElementTypes.ELEMENT) {
if (dir.arg) {
context.onError(
createDOMCompilerError(
DOMErrorCodes.X_V_MODEL_ARG_ON_ELEMENT,
dir.arg.loc
)
if (dir.arg) {
context.onError(
createDOMCompilerError(
DOMErrorCodes.X_V_MODEL_ARG_ON_ELEMENT,
dir.arg.loc
)
}
)
}
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
}
}
}
} 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 {
function checkDuplicatedValue() {
const value = findProp(node, 'value')
if (value) {
context.onError(
createDOMCompilerError(
DOMErrorCodes.X_V_MODEL_ON_INVALID_ELEMENT,
dir.loc
DOMErrorCodes.X_V_MODEL_UNNECESSARY_VALUE,
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
}

View File

@ -5,7 +5,9 @@ import {
createCallExpression,
createObjectExpression,
createSimpleExpression,
NodeTypes
NodeTypes,
createCompoundExpression,
ExpressionNode
} from '@vue/compiler-core'
import { V_ON_WITH_MODIFIERS, V_ON_WITH_KEYS } from '../runtimeHelpers'
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) => {
return baseTransform(dir, node, context, baseResult => {
const { modifiers } = dir
@ -64,6 +84,14 @@ export const transformOn: DirectiveTransform = (dir, node, context) => {
eventOptionModifiers
} = 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) {
handlerExp = createCallExpression(context.helper(V_ON_WITH_MODIFIERS), [
handlerExp,
@ -102,8 +130,7 @@ export const transformOn: DirectiveTransform = (dir, node, context) => {
}
return {
props: [createObjectProperty(key, handlerExp)],
needRuntime: false
props: [createObjectProperty(key, handlerExp)]
}
})
}

View File

@ -24,7 +24,6 @@ export const transformVText: DirectiveTransform = (dir, node, context) => {
createSimpleExpression(`textContent`, true, loc),
exp || createSimpleExpression('', true)
)
],
needRuntime: false
]
}
}

View File

@ -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))
)
}

View File

@ -2,7 +2,7 @@
exports[`source map 1`] = `
Object {
"mappings": ";;;;UAAA,aACE;IAAK,gCAAMA,WAAM",
"mappings": ";;;wBACE,aAA8B;IAAzB,aAAmB,4BAAbA,WAAM",
"names": Array [
"render",
],
@ -20,8 +20,8 @@ Object {
exports[`template errors 1`] = `
Array [
[SyntaxError: Invalid JavaScript expression. (2:13)],
[SyntaxError: v-bind is missing expression. (1:6)],
[SyntaxError: v-model can only be used on <input>, <textarea> and <select> elements. (2:17)],
[SyntaxError: Error parsing JavaScript expression: Unexpected token (1:3)],
[SyntaxError: v-bind is missing expression.],
[SyntaxError: v-model can only be used on <input>, <textarea> and <select> elements.],
]
`;

View File

@ -1,39 +1,38 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
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'
const _hoisted_1 = _imports_0 + '#fragment'
export default function render() {
const _ctx = this
return (openBlock(), createBlock(\\"use\\", { href: _hoisted_1 }))
export function render(_ctx, _cache) {
return (_openBlock(), _createBlock(\\"use\\", { href: _hoisted_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() {
const _ctx = this
return (openBlock(), createBlock(\\"use\\", { href: '' }))
export function render(_ctx, _cache) {
return (_openBlock(), _createBlock(\\"use\\", { href: '' }))
}"
`;
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_1 from 'fixtures/logo.png'
export default function render() {
const _ctx = this
return (openBlock(), createBlock(Fragment, null, [
createVNode(\\"img\\", { src: _imports_0 }),
createVNode(\\"img\\", { src: _imports_1 }),
createVNode(\\"img\\", { src: _imports_1 })
export function render(_ctx, _cache) {
return (_openBlock(), _createBlock(_Fragment, null, [
_createVNode(\\"img\\", { src: _imports_0 }),
_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 */))
}"
`;

View File

@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
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'
@ -12,37 +12,49 @@ const _hoisted_4 = _imports_0 + ', ' + _imports_0 + '2x'
const _hoisted_5 = _imports_0 + '2x, ' + _imports_0
const _hoisted_6 = _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() {
const _ctx = this
return (openBlock(), createBlock(Fragment, null, [
createVNode(\\"img\\", {
export function render(_ctx, _cache) {
return (_openBlock(), _createBlock(_Fragment, null, [
_createVNode(\\"img\\", {
src: \\"./logo.png\\",
srcset: _hoisted_1
}),
createVNode(\\"img\\", {
_createVNode(\\"img\\", {
src: \\"./logo.png\\",
srcset: _hoisted_2
}),
createVNode(\\"img\\", {
_createVNode(\\"img\\", {
src: \\"./logo.png\\",
srcset: _hoisted_3
}),
createVNode(\\"img\\", {
_createVNode(\\"img\\", {
src: \\"./logo.png\\",
srcset: _hoisted_4
}),
createVNode(\\"img\\", {
_createVNode(\\"img\\", {
src: \\"./logo.png\\",
srcset: _hoisted_5
}),
createVNode(\\"img\\", {
_createVNode(\\"img\\", {
src: \\"./logo.png\\",
srcset: _hoisted_6
}),
createVNode(\\"img\\", {
_createVNode(\\"img\\", {
src: \\"./logo.png\\",
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 */))
}"

View 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()
})
})
})

View File

@ -9,7 +9,7 @@ test('should work', () => {
expect(result.errors.length).toBe(0)
expect(result.source).toBe(source)
// should expose render fn
expect(result.code).toMatch(`export default function render()`)
expect(result.code).toMatch(`export function render(`)
})
test('preprocess pug', () => {
@ -23,7 +23,7 @@ body
</template>
`,
{ filename: 'example.vue', sourceMap: true }
).template as SFCTemplateBlock
).descriptor.template as SFCTemplateBlock
const result = compileTemplate({
filename: 'example.vue',
@ -35,10 +35,10 @@ body
})
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',
sourceMap: true
}).template as SFCTemplateBlock
}).descriptor.template as SFCTemplateBlock
const result = compileTemplate({
filename: 'example.vue',
@ -50,7 +50,7 @@ test('warn missing preprocessor', () => {
})
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
const { code: code1 } = compileTemplate({
...input,
@ -70,10 +70,10 @@ test('source map', () => {
`
<template>
<div><p>{{ render }}</p></div>
</template>
</template>
`,
{ filename: 'example.vue', sourceMap: true }
).template as SFCTemplateBlock
).descriptor.template as SFCTemplateBlock
const result = compileTemplate({
filename: 'example.vue',
@ -86,7 +86,7 @@ test('source map', () => {
test('template errors', () => {
const result = compileTemplate({
filename: 'example.vue',
source: `<div :foo
source: `<div :foo
:bar="a[" v-model="baz"/>`
})
expect(result.errors).toMatchSnapshot()
@ -100,7 +100,7 @@ test('preprocessor errors', () => {
</template>
`,
{ filename: 'example.vue', sourceMap: true }
).template as SFCTemplateBlock
).descriptor.template as SFCTemplateBlock
const result = compileTemplate({
filename: 'example.vue',

View File

@ -1,21 +1,40 @@
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', () => {
mockWarn()
describe('source map', () => {
test('style block', () => {
const style = parse(`<style>\n.color {\n color: red;\n }\n</style>\n`)
.styles[0]
// TODO need to actually test this with SourceMapConsumer
// Padding determines how many blank lines will there be before the style block
const padding = Math.round(Math.random() * 10)
const style = parse(
`${'\n'.repeat(padding)}<style>\n.color {\n color: red;\n }\n</style>\n`
).descriptor.styles[0]
expect(style.map).not.toBeUndefined()
const consumer = new SourceMapConsumer(style.map!)
consumer.eachMapping(mapping => {
expect(mapping.originalLine - mapping.generatedLine).toBe(padding)
})
})
test('script block', () => {
const script = parse(`<script>\nconsole.log(1)\n }\n</script>\n`).script
// TODO need to actually test this with SourceMapConsumer
// Padding determines how many blank lines will there be before the style block
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()
const consumer = new SourceMapConsumer(script!.map!)
consumer.eachMapping(mapping => {
expect(mapping.originalLine - mapping.generatedLine).toBe(padding)
})
})
})
@ -30,12 +49,12 @@ export default {}
<style>
h1 { color: red }
</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.script!.content).toBe('\nexport default {}\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(
Array(3 + 1).join('//\n') + '\nexport default {}\n'
)
@ -43,7 +62,7 @@ h1 { color: red }
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(
Array(3 + 1).join('//\n') + '\nexport default {}\n'
)
@ -51,7 +70,7 @@ h1 { color: red }
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(
`<template>\n<div></div>\n</template>\n<script>`.replace(/./g, ' ') +
'\nexport default {}\n'
@ -65,13 +84,55 @@ h1 { color: red }
})
test('should ignore nodes with no content', () => {
expect(parse(`<template/>`).template).toBe(null)
expect(parse(`<script/>`).script).toBe(null)
expect(parse(`<style/>`).styles.length).toBe(0)
expect(parse(`<custom/>`).customBlocks.length).toBe(0)
expect(parse(`<template/>`).descriptor.template).toBe(null)
expect(parse(`<script/>`).descriptor.script).toBe(null)
expect(parse(`<style/>`).descriptor.styles.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', () => {
parse(`<template><div/></template><template><div/></template>`)
expect(

View File

@ -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 { transformElement } from '../../compiler-core/src/transforms/transformElement'
import { transformBind } from '../../compiler-core/src/transforms/vBind'
function compileWithAssetUrls(template: string) {
const ast = parse(template)
const ast = baseParse(template)
transform(ast, {
nodeTransforms: [transformAssetUrl, transformElement],
directiveTransforms: {
@ -20,6 +20,8 @@ describe('compiler sfc: transform asset url', () => {
<img src="./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()

View File

@ -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 { transformElement } from '../../compiler-core/src/transforms/transformElement'
import { transformBind } from '../../compiler-core/src/transforms/vBind'
function compileWithSrcset(template: string) {
const ast = parse(template)
const ast = baseParse(template)
transform(ast, {
nodeTransforms: [transformSrcset, transformElement],
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 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()

View File

@ -1,12 +1,12 @@
{
"name": "@vue/compiler-sfc",
"version": "3.0.0-alpha.0",
"version": "3.0.0-alpha.11",
"description": "@vue/compiler-sfc",
"main": "dist/compiler-sfc.cjs.js",
"types": "dist/compiler-sfc.d.ts",
"files": [
"dist"
],
"types": "dist/compiler-sfc.d.ts",
"buildOptions": {
"prod": false,
"formats": [
@ -15,7 +15,7 @@
},
"repository": {
"type": "git",
"url": "git+https://github.com/vuejs/vue.git"
"url": "git+https://github.com/vuejs/vue-next.git"
},
"keywords": [
"vue"
@ -23,19 +23,24 @@
"author": "Evan You",
"license": "MIT",
"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": {
"@vue/compiler-core": "3.0.0-alpha.0",
"@vue/compiler-dom": "3.0.0-alpha.0",
"@vue/shared": "3.0.0-alpha.11",
"@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",
"hash-sum": "^2.0.0",
"lru-cache": "^5.1.1",
"merge-source-map": "^1.1.0",
"postcss": "^7.0.21",
"postcss-selector-parser": "^6.0.2",
"source-map": "^0.7.3"
"source-map": "^0.6.1"
},
"devDependencies": {
"@types/consolidate": "^0.14.0",

View File

@ -10,7 +10,7 @@ import {
} from './stylePreprocessors'
import { RawSourceMap } from 'source-map'
export interface StyleCompileOptions {
export interface SFCStyleCompileOptions {
source: string
filename: string
id: string
@ -23,11 +23,11 @@ export interface StyleCompileOptions {
postcssPlugins?: any[]
}
export interface AsyncStyleCompileOptions extends StyleCompileOptions {
export interface SFCAsyncStyleCompileOptions extends SFCStyleCompileOptions {
isAsync?: boolean
}
export interface StyleCompileResults {
export interface SFCStyleCompileResults {
code: string
map: RawSourceMap | undefined
rawResult: LazyResult | Result | undefined
@ -35,22 +35,25 @@ export interface StyleCompileResults {
}
export function compileStyle(
options: StyleCompileOptions
): StyleCompileResults {
return doCompileStyle({ ...options, isAsync: false }) as StyleCompileResults
options: SFCStyleCompileOptions
): SFCStyleCompileResults {
return doCompileStyle({
...options,
isAsync: false
}) as SFCStyleCompileResults
}
export function compileStyleAsync(
options: StyleCompileOptions
): Promise<StyleCompileResults> {
options: SFCStyleCompileOptions
): Promise<SFCStyleCompileResults> {
return doCompileStyle({ ...options, isAsync: true }) as Promise<
StyleCompileResults
SFCStyleCompileResults
>
}
export function doCompileStyle(
options: AsyncStyleCompileOptions
): StyleCompileResults | Promise<StyleCompileResults> {
options: SFCAsyncStyleCompileOptions
): SFCStyleCompileResults | Promise<SFCStyleCompileResults> {
const {
filename,
id,
@ -131,7 +134,7 @@ export function doCompileStyle(
}
function preprocess(
options: StyleCompileOptions,
options: SFCStyleCompileOptions,
preprocessor: StylePreprocessor
): StylePreprocessorResults {
return preprocessor.render(options.source, options.map, {

View File

@ -2,9 +2,11 @@ import {
CompilerOptions,
CodegenResult,
CompilerError,
NodeTransform
NodeTransform,
ParserOptions,
RootNode
} from '@vue/compiler-core'
import { RawSourceMap } from 'source-map'
import { SourceMapConsumer, SourceMapGenerator, RawSourceMap } from 'source-map'
import {
transformAssetUrl,
AssetURLOptions,
@ -14,7 +16,12 @@ import { transformSrcset } from './templateTransformSrcset'
import { isObject } from '@vue/shared'
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
source: string
tips: string[]
@ -22,13 +29,11 @@ export interface TemplateCompileResults {
map?: RawSourceMap
}
export interface TemplateCompiler {
compile(template: string, options: CompilerOptions): CodegenResult
}
export interface TemplateCompileOptions {
export interface SFCTemplateCompileOptions {
source: string
filename: string
ssr?: boolean
inMap?: RawSourceMap
compiler?: TemplateCompiler
compilerOptions?: CompilerOptions
preprocessLang?: string
@ -37,7 +42,7 @@ export interface TemplateCompileOptions {
}
function preprocess(
{ source, filename, preprocessOptions }: TemplateCompileOptions,
{ source, filename, preprocessOptions }: SFCTemplateCompileOptions,
preprocessor: any
): string {
// Consolidate exposes a callback based API, but the callback is in fact
@ -59,8 +64,8 @@ function preprocess(
}
export function compileTemplate(
options: TemplateCompileOptions
): TemplateCompileResults {
options: SFCTemplateCompileOptions
): SFCTemplateCompileResults {
const { preprocessLang } = options
const preprocessor =
preprocessLang && consolidate[preprocessLang as keyof typeof consolidate]
@ -100,11 +105,13 @@ export function compileTemplate(
function doCompileTemplate({
filename,
inMap,
source,
compiler = require('@vue/compiler-dom'),
ssr = false,
compiler = ssr ? require('@vue/compiler-ssr') : require('@vue/compiler-dom'),
compilerOptions = {},
transformAssetUrls
}: TemplateCompileOptions): TemplateCompileResults {
}: SFCTemplateCompileOptions): SFCTemplateCompileResults {
const errors: CompilerError[] = []
let nodeTransforms: NodeTransform[] = []
@ -117,7 +124,7 @@ function doCompileTemplate({
nodeTransforms = [transformAssetUrl, transformSrcset]
}
const { code, map } = compiler.compile(source, {
let { code, map } = compiler.compile(source, {
mode: 'module',
prefixIdentifiers: true,
hoistStatic: true,
@ -128,5 +135,91 @@ function doCompileTemplate({
sourceMap: true,
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 }
}
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
}
}
})
}

View File

@ -14,8 +14,12 @@ export {
} from './parse'
export {
TemplateCompiler,
TemplateCompileOptions,
TemplateCompileResults
SFCTemplateCompileOptions,
SFCTemplateCompileResults
} from './compileTemplate'
export { StyleCompileOptions, StyleCompileResults } from './compileStyle'
export { CompilerOptions, generateCodeFrame } from '@vue/compiler-core'
export { SFCStyleCompileOptions, SFCStyleCompileResults } from './compileStyle'
export {
CompilerOptions,
CompilerError,
generateCodeFrame
} from '@vue/compiler-core'

Some files were not shown because too many files have changed in this diff Show More