Merge remote-tracking branch 'github/master' into changing_unwrap_ref
# Conflicts: # packages/reactivity/src/ref.ts # packages/runtime-core/__tests__/apiTemplateRef.spec.ts # packages/runtime-core/src/apiWatch.ts
This commit is contained in:
commit
5ae74144f2
@ -1,37 +1,63 @@
|
||||
version: 2
|
||||
|
||||
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
2
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
open_collective: vuejs
|
||||
patreon: evanyou
|
65
.github/contributing.md
vendored
65
.github/contributing.md
vendored
@ -19,10 +19,12 @@ Hi! I'm really excited that you are interested in contributing to Vue.js. Before
|
||||
- Checkout a topic branch from a base branch, e.g. `master`, and merge back against that branch.
|
||||
|
||||
- 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
16
.github/issue-template.md
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
<!--
|
||||
IMPORTANT: Please use the following link to create a new issue:
|
||||
|
||||
https://new-issue.vuejs.org/?repo=vuejs/vue-next
|
||||
|
||||
If your issue was not created using the app above, it will be closed immediately.
|
||||
|
||||
中文用户请注意:
|
||||
请使用上面的链接来创建新的 issue。如果不是用上述工具创建的 issue 会被自动关闭。
|
||||
-->
|
||||
|
||||
<!--
|
||||
Love vuejs? Please consider supporting us via Patreon or OpenCollective:
|
||||
👉 https://www.patreon.com/evanyou
|
||||
👉 https://opencollective.com/vuejs/donate
|
||||
-->
|
7
.ls-lint.yml
Normal file
7
.ls-lint.yml
Normal file
@ -0,0 +1,7 @@
|
||||
ls:
|
||||
packages/*/{src,__tests__}:
|
||||
.js: kebab-case
|
||||
.ts: camelCase | PascalCase
|
||||
.d.ts: camelCase
|
||||
.spec.ts: camelCase | PascalCase
|
||||
.mock.ts: camelCase
|
537
CHANGELOG.md
Normal file
537
CHANGELOG.md
Normal file
@ -0,0 +1,537 @@
|
||||
# [3.0.0-alpha.11](https://github.com/vuejs/vue-next/compare/v3.0.0-alpha.10...v3.0.0-alpha.11) (2020-04-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler:** fix pre tag whitespace handling ([7f30cb5](https://github.com/vuejs/vue-next/commit/7f30cb577257ad5765261bbffa3cae862259fcab)), closes [#908](https://github.com/vuejs/vue-next/issues/908)
|
||||
* **compiler-core/slots:** should support on-component named slots ([a022b63](https://github.com/vuejs/vue-next/commit/a022b63605820c97923413ee457ba1fb69a5221e))
|
||||
* **compiler-sfc:** always use offset for template block sourcemaps ([#911](https://github.com/vuejs/vue-next/issues/911)) ([db50009](https://github.com/vuejs/vue-next/commit/db5000935306214b31e33865cd57935e80e27d41))
|
||||
* **inject:** allow default value to be `undefined` ([#894](https://github.com/vuejs/vue-next/issues/894)) ([94562da](https://github.com/vuejs/vue-next/commit/94562daea70fde33a340bb7b57746523c3660a8e)), closes [#892](https://github.com/vuejs/vue-next/issues/892)
|
||||
* **portal:** portal should always remove its children when unmounted ([16cd8ee](https://github.com/vuejs/vue-next/commit/16cd8eee7839cc4613f17642bf37b39f7bdf1fce))
|
||||
* **reactivity:** scheduled effect should not execute if stopped ([0764c33](https://github.com/vuejs/vue-next/commit/0764c33d3da8c06d472893a4e451e33394726a42)), closes [#910](https://github.com/vuejs/vue-next/issues/910)
|
||||
* **runtime-core:** support attr merging on child with root level comments ([e42cb54](https://github.com/vuejs/vue-next/commit/e42cb543947d4286115b6adae6e8a5741d909f14)), closes [#904](https://github.com/vuejs/vue-next/issues/904)
|
||||
* **runtime-dom:** v-cloak should be removed after compile on the root element ([#893](https://github.com/vuejs/vue-next/issues/893)) ([0ed147d](https://github.com/vuejs/vue-next/commit/0ed147d33610b86af72cbadcc4b32e6069bcaf08)), closes [#890](https://github.com/vuejs/vue-next/issues/890)
|
||||
* **runtome-dom:** properly support creating customized built-in element ([b1d0b04](https://github.com/vuejs/vue-next/commit/b1d0b046afb1e8f4640d8d80b6eeaf9f89e892f7))
|
||||
* **transition:** warn only when there is more than one rendered child ([#903](https://github.com/vuejs/vue-next/issues/903)) ([37b1dc8](https://github.com/vuejs/vue-next/commit/37b1dc8242608b072d14fd2a5e52f5d40829ea52))
|
||||
* **types:** allow use PropType with Function ([#915](https://github.com/vuejs/vue-next/issues/915)) ([026eb72](https://github.com/vuejs/vue-next/commit/026eb729f3d1566e95f2f4253d76c20e86d1ec9b)), closes [#748](https://github.com/vuejs/vue-next/issues/748)
|
||||
* **types:** export missing types from runtime-core ([#889](https://github.com/vuejs/vue-next/issues/889)) ([412ec86](https://github.com/vuejs/vue-next/commit/412ec86128fa33fa41ce435c493fd8275a785fea))
|
||||
* **types/reactivity:** add generics constraint for markNonReactive ([f3b6559](https://github.com/vuejs/vue-next/commit/f3b6559408fb42ff6dc0c67001c9c67093f2b059)), closes [#917](https://github.com/vuejs/vue-next/issues/917)
|
||||
|
||||
|
||||
### Code Refactoring
|
||||
|
||||
* **runtime-core:** adjust attr fallthrough behavior ([21bcdec](https://github.com/vuejs/vue-next/commit/21bcdec9435700cac98868a36716b49a7766c48d))
|
||||
* rename `<portal>` to `<teleport>` ([eee5095](https://github.com/vuejs/vue-next/commit/eee50956924d7d2c916cdb8b99043da616e53af5))
|
||||
* **runtime-core:** rename `createAsyncComponent` to `defineAsyncComponent` ([#888](https://github.com/vuejs/vue-next/issues/888)) ([ebc5873](https://github.com/vuejs/vue-next/commit/ebc587376ca1fb4bb8a20d4137332740605753c8))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **asyncComponent:** retry support ([c01930e](https://github.com/vuejs/vue-next/commit/c01930e60b4abf481900cdfcc2ba422890c41656))
|
||||
* **compiler-core:** export `transformElement` from compiler-core ([#907](https://github.com/vuejs/vue-next/issues/907)) ([20f4965](https://github.com/vuejs/vue-next/commit/20f4965b45d410a2fe95310ecf7293b2b7f46f36))
|
||||
* **compiler-core:** support v-is ([b8ffbff](https://github.com/vuejs/vue-next/commit/b8ffbffaf771c259848743cf4eb1a5ea31795aaa))
|
||||
* **portal:** hydration support for portal disabled mode ([b74bab2](https://github.com/vuejs/vue-next/commit/b74bab216c3be68ab046451cf5e5b5bec5f19483))
|
||||
* **portal:** SSR support for multi portal shared target ([e866434](https://github.com/vuejs/vue-next/commit/e866434f0c54498dd0fc47d48287a1d0ada36388))
|
||||
* **portal:** SSR support for portal disabled prop ([9ed9bf3](https://github.com/vuejs/vue-next/commit/9ed9bf3687a770aebc265839065832761e6bafa1))
|
||||
* **portal:** support disabled prop ([8ce3da0](https://github.com/vuejs/vue-next/commit/8ce3da0104db9bdd89929724c6d841ac3dfb7336))
|
||||
* **portal:** support multiple portal appending to same target ([aafb880](https://github.com/vuejs/vue-next/commit/aafb880a0a9e023b62cf8fb3ae269b31f22ac84e))
|
||||
* **reactivity:** add effect to public api ([#909](https://github.com/vuejs/vue-next/issues/909)) ([6fba241](https://github.com/vuejs/vue-next/commit/6fba2418507d9c65891e8d14bd63736adb377556))
|
||||
* **runtime-core:** config.performance tracing support ([e93e426](https://github.com/vuejs/vue-next/commit/e93e426bfad13f40c8f1d80b8f45ac5d0926c2fc))
|
||||
* **runtime-core:** emits validation and warnings ([c7c3a6a](https://github.com/vuejs/vue-next/commit/c7c3a6a3bef6275be8f9f8873358421017bb5386))
|
||||
* **runtime-core:** failed component resolution should fallback to native element ([cb31eb4](https://github.com/vuejs/vue-next/commit/cb31eb4d0a0afdd2abf9e3897d9aac447dd0264b))
|
||||
* **runtime-core:** support app.config.globalProperties ([27873db](https://github.com/vuejs/vue-next/commit/27873dbe1c09ac6a058d815949a4e13831513fd0))
|
||||
* **runtime-core:** type and attr fallthrough support for emits option ([bf473a6](https://github.com/vuejs/vue-next/commit/bf473a64eacab21d734d556c66cc190aa4ff902d))
|
||||
* **templateRef:** should work with direct reactive property ([449ab03](https://github.com/vuejs/vue-next/commit/449ab039feb10df7179898b13ecc45028a043002)), closes [#901](https://github.com/vuejs/vue-next/issues/901)
|
||||
* **templateRef:** support template ref for all vnode types ([55b364d](https://github.com/vuejs/vue-next/commit/55b364decc903a6c7fccd1cdcdcfc79948c848a2))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* **runtime-core:** attribute fallthrough behavior has been adjusted
|
||||
according to https://github.com/vuejs/rfcs/pull/154
|
||||
* `<portal>` has been renamed to `<teleport>`.
|
||||
|
||||
`target` prop is also renamed to `to`, so the new usage will be:
|
||||
|
||||
```html
|
||||
<Teleport to="#modal-layer" :disabled="isMobile">
|
||||
<div class="modal">
|
||||
hello
|
||||
</div>
|
||||
</Teleport>
|
||||
```
|
||||
|
||||
The primary reason for the renaming is to avoid potential naming
|
||||
conflict with [native portals](https://wicg.github.io/portals/).
|
||||
* **asyncComponent:** async component `error` and `loading` options have been
|
||||
renamed to `errorComponent` and `loadingComponent` respectively.
|
||||
* **runtime-core:** `createAsyncComponent` has been renamed to `defineAsyncComponent` for consistency with `defineComponent`.
|
||||
|
||||
|
||||
|
||||
# [3.0.0-alpha.10](https://github.com/vuejs/vue-next/compare/v3.0.0-alpha.9...v3.0.0-alpha.10) (2020-03-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix option merge global mixins presence check ([10ad965](https://github.com/vuejs/vue-next/commit/10ad965100a88e28cb528690f2e09070fefc8872))
|
||||
* **compiler-core:** assign patchFlag for template v-if fragment ([a1da9c2](https://github.com/vuejs/vue-next/commit/a1da9c28a0a7030124b1deb9369685760c67be47)), closes [#850](https://github.com/vuejs/vue-next/issues/850)
|
||||
* **compiler-core:** support interpolation in RCDATA mode (e.g. textarea) ([0831b98](https://github.com/vuejs/vue-next/commit/0831b98eac344d9bdfd6f6e922902adb91ea180a))
|
||||
* **keep-alive:** should update re-activated component with latest props ([1237387](https://github.com/vuejs/vue-next/commit/123738727a0af54fd632bf838dc3aa024722ee41))
|
||||
* **reactivity:** should not observe frozen objects ([1b2149d](https://github.com/vuejs/vue-next/commit/1b2149dbb2dd224d01e90c1a9332bfe67aa465ce)), closes [#867](https://github.com/vuejs/vue-next/issues/867)
|
||||
* **reactivity:** should not trigger map keys iteration when keys did not change ([45ba06a](https://github.com/vuejs/vue-next/commit/45ba06ac5f49876b4f05e5996f595b2c4a761f47)), closes [#877](https://github.com/vuejs/vue-next/issues/877)
|
||||
* **runtime-core:** fix boolean props validation ([3b282e7](https://github.com/vuejs/vue-next/commit/3b282e7e3c96786af0a5ff61822882d1ed3f4db3))
|
||||
* **runtime-dom:** invalid lineGradient svg tag ([#863](https://github.com/vuejs/vue-next/issues/863)) ([d425818](https://github.com/vuejs/vue-next/commit/d425818901428ff919a0179fc910410cbcfa119b)), closes [#862](https://github.com/vuejs/vue-next/issues/862)
|
||||
* **TransitionGroup:** ignore comment node when warn (fix[#869](https://github.com/vuejs/vue-next/issues/869)) ([#875](https://github.com/vuejs/vue-next/issues/875)) ([0dba5d4](https://github.com/vuejs/vue-next/commit/0dba5d44e60d33b909f4e4d05663c7ddf746a1f2))
|
||||
* do not drop SFC runtime behavior code in global builds ([4c1a193](https://github.com/vuejs/vue-next/commit/4c1a193617bee8ace6fad289b78e9d2557cb081e)), closes [#873](https://github.com/vuejs/vue-next/issues/873)
|
||||
* dynamic component fallback to native element ([f529dbd](https://github.com/vuejs/vue-next/commit/f529dbde236e9eaedbded78e926951a189234f9c)), closes [#870](https://github.com/vuejs/vue-next/issues/870)
|
||||
* **runtime-core:** fix component proxy props presence check ([b3890a9](https://github.com/vuejs/vue-next/commit/b3890a93e39342fd16e5fd72c59f361fc211309c)), closes [#864](https://github.com/vuejs/vue-next/issues/864)
|
||||
* **suspense:** clear effects on suspense resolve ([ebc1ca8](https://github.com/vuejs/vue-next/commit/ebc1ca8eff82789987c09a9f6a934898b00153ff))
|
||||
* **transition:** fix duration prop validation ([0dc2478](https://github.com/vuejs/vue-next/commit/0dc24785699101fa24d2a68786feaaac8a887520)), closes [#868](https://github.com/vuejs/vue-next/issues/868)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **asyncComponent:** SSR/hydration support for async component ([cba2f1a](https://github.com/vuejs/vue-next/commit/cba2f1aadbd0d4ae246040ecd5a91d8dd4e8fd1a))
|
||||
* **runtime-core:** async component support ([c3bb316](https://github.com/vuejs/vue-next/commit/c3bb3169f497fc834654d8ae700f18b1a6613127))
|
||||
* **runtime-core:** support `config.optionMergeStrategies` ([528621b](https://github.com/vuejs/vue-next/commit/528621ba41b1d7113940077574217d01d182b35f))
|
||||
* add hook for transforming h's arguments ([#851](https://github.com/vuejs/vue-next/issues/851)) ([b7d1e0f](https://github.com/vuejs/vue-next/commit/b7d1e0fa2ffe4561a589580eca6e92171c311347))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **transform-vif:** don't need to createBlock for a component ([#853](https://github.com/vuejs/vue-next/issues/853)) ([a3601e9](https://github.com/vuejs/vue-next/commit/a3601e9fa73d10f524ed3bdf3ae44df8847c1230))
|
||||
|
||||
|
||||
|
||||
# [3.0.0-alpha.9](https://github.com/vuejs/vue-next/compare/v3.0.0-alpha.8...v3.0.0-alpha.9) (2020-03-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **build:** remove __RUNTIME_COMPILE__ flag ([206640a](https://github.com/vuejs/vue-next/commit/206640a2d859a9ce9c19f22e201692f15a8d1da3)), closes [#817](https://github.com/vuejs/vue-next/issues/817)
|
||||
* **compiler-core:** fix property shorthand detection ([586e5bb](https://github.com/vuejs/vue-next/commit/586e5bb8003916ba6be9b3394087df80328657f4)), closes [#845](https://github.com/vuejs/vue-next/issues/845)
|
||||
* **compiler-ssr:** fix input w/ v-bind="obj" codegen ([3b40fc5](https://github.com/vuejs/vue-next/commit/3b40fc56dba56a5c1085582d11f3287e9317a151))
|
||||
* **compiler-ssr:** should pass necessary tag names for dynamic v-bind ([a46f3b3](https://github.com/vuejs/vue-next/commit/a46f3b354d451a857df750a318bd0536338008cd))
|
||||
* **runtime-core:** always set invalid vnode type ([#820](https://github.com/vuejs/vue-next/issues/820)) ([28a9bee](https://github.com/vuejs/vue-next/commit/28a9beed1624de9812e0f4ce9b63f7f3ed2c6db8))
|
||||
* **runtime-core:** empty boolean props ([#844](https://github.com/vuejs/vue-next/issues/844)) ([c7ae269](https://github.com/vuejs/vue-next/commit/c7ae2699724bd5206ce7d2db73b86c1ef5947641)), closes [#843](https://github.com/vuejs/vue-next/issues/843)
|
||||
* **runtime-core:** pass instance proxy as data() argument ([#828](https://github.com/vuejs/vue-next/issues/828)) ([d9dd1d8](https://github.com/vuejs/vue-next/commit/d9dd1d8a0ac81d7d463e0788bb2e75b2d4866db6))
|
||||
* **runtime-dom:** patch xlink attribute ([#842](https://github.com/vuejs/vue-next/issues/842)) ([d318576](https://github.com/vuejs/vue-next/commit/d318576d74f8756e471942ff44d2af2a4661d775))
|
||||
* simplify and use correct ctx in withCtx ([4dc8ffc](https://github.com/vuejs/vue-next/commit/4dc8ffc3788c38aff3e4c0f271d0ca111f723140))
|
||||
* **runtime-core:** pass prev value to hostPatchProp ([#809](https://github.com/vuejs/vue-next/issues/809)) ([cd34603](https://github.com/vuejs/vue-next/commit/cd34603864142d5468486ec3f379679b22014a1b)), closes [#808](https://github.com/vuejs/vue-next/issues/808)
|
||||
* **runtime-core:** should allow empty string and 0 as valid vnode key ([#807](https://github.com/vuejs/vue-next/issues/807)) ([54a0e93](https://github.com/vuejs/vue-next/commit/54a0e93c276f95a35b3bd6510a7f52d967fd3b7f))
|
||||
* **types:** app.component should accept defineComponent return type ([#822](https://github.com/vuejs/vue-next/issues/822)) ([1e9d131](https://github.com/vuejs/vue-next/commit/1e9d1319c3f66a0a7430a4f6ac7b508486894b6b)), closes [#730](https://github.com/vuejs/vue-next/issues/730)
|
||||
|
||||
|
||||
### Code Refactoring
|
||||
|
||||
* **runtime-core:** adjust patchProp value arguments order ([ca5f39e](https://github.com/vuejs/vue-next/commit/ca5f39ee3501a1d9cacdb74108318c15ee7c0abb))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **compiler-core:** wrap slot functions with render context ([ecd7ce6](https://github.com/vuejs/vue-next/commit/ecd7ce60d5234a7a0dbc11add6a690c3f9ff0617))
|
||||
* **compiler-sfc:** add ssr option ([3b2d236](https://github.com/vuejs/vue-next/commit/3b2d23671409f8ac358252311bf5212882fa985a))
|
||||
* **runtime-core:** add special property to get class component options ([#821](https://github.com/vuejs/vue-next/issues/821)) ([dd17fa1](https://github.com/vuejs/vue-next/commit/dd17fa1c9071b9685c379e1b12102214b757cf35))
|
||||
* **runtime-core:** implement RFC-0020 ([bb7fa3d](https://github.com/vuejs/vue-next/commit/bb7fa3dabce73de63d016c75f1477e7d8bed8858))
|
||||
* **runtime-core:** set context for manual slot functions as well ([8a58dce](https://github.com/vuejs/vue-next/commit/8a58dce6034944b18c2e507b5d9ab8177f60e269))
|
||||
* **server-renderer:** render suspense in vnode mode ([#727](https://github.com/vuejs/vue-next/issues/727)) ([589aeb4](https://github.com/vuejs/vue-next/commit/589aeb402c58f463cc32d5e7728b56614bc9bf33))
|
||||
* **ssr:** compiler-ssr support for Suspense ([80c625d](https://github.com/vuejs/vue-next/commit/80c625dce33610e53c953e9fb8fde26e3e10e358))
|
||||
* **ssr:** hide comment anchors during hydration in dev mode ([cad5bcc](https://github.com/vuejs/vue-next/commit/cad5bcce40b9f2aaa520ccbd377cd5419650e55f))
|
||||
* **ssr:** improve fragment mismatch handling ([60ed4e7](https://github.com/vuejs/vue-next/commit/60ed4e7e0821a2932660b87fbf8d5ca953e0e073))
|
||||
* **ssr:** support getSSRProps for vnode directives ([c450ede](https://github.com/vuejs/vue-next/commit/c450ede12d1a93a70271a2fe7fcb6f8efcf1cd4c))
|
||||
* **ssr/suspense:** suspense hydration ([a3cc970](https://github.com/vuejs/vue-next/commit/a3cc970030579f2c55d893d6e83bbc05324adad4))
|
||||
* **types:** export `ErrorTypes` ([#840](https://github.com/vuejs/vue-next/issues/840)) ([760c3e0](https://github.com/vuejs/vue-next/commit/760c3e0fd67f6360995cdbb125f9eae4e024f3af))
|
||||
|
||||
|
||||
### Reverts
|
||||
|
||||
* Revert "refactor(directives): remove binding.instance" ([2370166](https://github.com/vuejs/vue-next/commit/23701666cb487e55d05b74d66990361051715ba4))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* **runtime-core:** data no longer supports object format (per RFC-0020)
|
||||
* **runtime-core:** `RendererOptions.patchProp` arguments order has changed
|
||||
|
||||
The `prevValue` and `nextValue` position has been swapped to keep it
|
||||
consistent with other functions in the renderer implementation. This
|
||||
only affects custom renderers using the `createRenderer` API.
|
||||
|
||||
|
||||
|
||||
# [3.0.0-alpha.8](https://github.com/vuejs/vue-next/compare/v3.0.0-alpha.7...v3.0.0-alpha.8) (2020-03-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **directives:** ignore invalid directive hooks ([7971b04](https://github.com/vuejs/vue-next/commit/7971b0468c81483dd7026204518f7c03187d13c4)), closes [#795](https://github.com/vuejs/vue-next/issues/795)
|
||||
* **portal:** fix portal placeholder text ([4397528](https://github.com/vuejs/vue-next/commit/439752822c175c737e58896e0f365f2b02bab577))
|
||||
* **reactivity:** allow effect trigger inside no-track execution contexts ([274f81c](https://github.com/vuejs/vue-next/commit/274f81c5db83f0f77e1aba3240b2134a2474a72f)), closes [#804](https://github.com/vuejs/vue-next/issues/804)
|
||||
* **reactivity:** Map/Set identity methods should work even if raw value contains reactive entries ([cc69fd7](https://github.com/vuejs/vue-next/commit/cc69fd72e3f9ef3572d2be40af71d22232e1b9af)), closes [#799](https://github.com/vuejs/vue-next/issues/799)
|
||||
* **reactivity:** should not trigger length dependency on Array delete ([a306658](https://github.com/vuejs/vue-next/commit/a3066581f3014aae31f2d96b96428100f1674166)), closes [#774](https://github.com/vuejs/vue-next/issues/774)
|
||||
* **runtime-core:** ensure inhertied attrs update on optimized child root ([6810d14](https://github.com/vuejs/vue-next/commit/6810d1402e214a12fa274ff5fb7475bad002d1b1)), closes [#677](https://github.com/vuejs/vue-next/issues/677) [#784](https://github.com/vuejs/vue-next/issues/784)
|
||||
* **slots:** fix conditional slot ([3357ff4](https://github.com/vuejs/vue-next/commit/3357ff438c6ff0d4fea67923724dd3cb99ff2756)), closes [#787](https://github.com/vuejs/vue-next/issues/787)
|
||||
* **ssr:** fix ssr on-the-fly compilation + slot fallback branch helper injection ([3be3785](https://github.com/vuejs/vue-next/commit/3be3785f945253918469da456a14a2d9381bcbd0))
|
||||
|
||||
|
||||
### Code Refactoring
|
||||
|
||||
* **runtime-core:** adjust attr fallthrough behavior ([e1660f4](https://github.com/vuejs/vue-next/commit/e1660f4338fbf4d2a434e13193a58e00f844379b)), closes [#749](https://github.com/vuejs/vue-next/issues/749)
|
||||
* **runtime-core:** revert setup() result reactive conversion ([e67f655](https://github.com/vuejs/vue-next/commit/e67f655b2687042fcc74dc0993581405abed56de))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **compiler-core:** switch to @babel/parser for expression parsing ([8449a97](https://github.com/vuejs/vue-next/commit/8449a9727c942b6049c9e577c7c15b43fdca2867))
|
||||
* **compiler-ssr:** compile portal ([#775](https://github.com/vuejs/vue-next/issues/775)) ([d8ed0e7](https://github.com/vuejs/vue-next/commit/d8ed0e7fbf9bbe734667eb94e809235e79e431eb))
|
||||
* **ssr:** hydration mismatch handling ([91269da](https://github.com/vuejs/vue-next/commit/91269da52c30abf6c50312555b715f5360224bb0))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* **runtime-core:** adjust attr fallthrough behavior
|
||||
|
||||
Updated per pending RFC https://github.com/vuejs/rfcs/pull/137
|
||||
|
||||
- Implicit fallthrough now by default only applies for a whitelist
|
||||
of attributes (class, style, event listeners, a11y attributes, and
|
||||
data attributes).
|
||||
|
||||
- Fallthrough is now applied regardless of whether the component has
|
||||
* **runtime-core:** revert setup() result reactive conversion
|
||||
|
||||
Revert 6b10f0c & a840e7d. The motivation of the original change was
|
||||
avoiding unnecessary deep conversions, but that can be achieved by
|
||||
explicitly marking values non-reactive via `markNonReactive`.
|
||||
|
||||
Removing the reactive conversion behavior leads to an usability
|
||||
issue in that plain objects containing refs (which is what most
|
||||
composition functions will return), when exposed as a nested
|
||||
property from `setup()`, will not unwrap the refs in templates. This
|
||||
goes against the "no .value in template" intuition and the only
|
||||
workaround requires users to manually wrap it again with `reactive()`.
|
||||
|
||||
So in this commit we are reverting to the previous behavior where
|
||||
objects returned from `setup()` are implicitly wrapped with
|
||||
`reactive()` for deep ref unwrapping.
|
||||
|
||||
|
||||
|
||||
# [3.0.0-alpha.7](https://github.com/vuejs/vue-next/compare/v3.0.0-alpha.6...v3.0.0-alpha.7) (2020-02-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **renderSlot:** set slot render as a STABLE_FRAGMENT ([#776](https://github.com/vuejs/vue-next/issues/776)) ([8cb0b83](https://github.com/vuejs/vue-next/commit/8cb0b8308801159177ec16ab5a3e23672c4c1d00)), closes [#766](https://github.com/vuejs/vue-next/issues/766)
|
||||
* **runtime-core:** fix slot fallback + slots typing ([4a5b91b](https://github.com/vuejs/vue-next/commit/4a5b91bd1faec76bbaa0522b095f4a07ca88a9e5)), closes [#773](https://github.com/vuejs/vue-next/issues/773)
|
||||
* **runtime-core:** make watchEffect ignore deep option ([#765](https://github.com/vuejs/vue-next/issues/765)) ([19a799c](https://github.com/vuejs/vue-next/commit/19a799c28b149b14e85d9e2081fa65ed58d108ba))
|
||||
* **runtime-core:** set appContext.provides to Object.create(null) ([#781](https://github.com/vuejs/vue-next/issues/781)) ([04f83fa](https://github.com/vuejs/vue-next/commit/04f83fa6810e07915e98b94c954ff0c1859aaa49))
|
||||
* **template-explorer:** rename watch -> watchEffect ([#780](https://github.com/vuejs/vue-next/issues/780)) ([59393dd](https://github.com/vuejs/vue-next/commit/59393dd75766720330cb69e22086c97a392dbbe4))
|
||||
* **template-ref:** fix string template refs inside slots ([3eab143](https://github.com/vuejs/vue-next/commit/3eab1438432a3bab15ccf2f6092fc3e4355f3cdd))
|
||||
* **types:** ref value type unwrapping should happen at creation time ([d4c6957](https://github.com/vuejs/vue-next/commit/d4c6957e2d8ac7920a649f3a3576689cd5e1099f))
|
||||
* **types:** shallowRef should not unwrap value type ([3206e5d](https://github.com/vuejs/vue-next/commit/3206e5dfe58fd0e93644d13929558d71c5171888))
|
||||
|
||||
|
||||
### Code Refactoring
|
||||
|
||||
* **directives:** remove binding.instance ([52cc7e8](https://github.com/vuejs/vue-next/commit/52cc7e823148289b3dcdcb6b521984ab815fce79))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* **directives:** custom directive bindings no longer expose instance
|
||||
|
||||
This is a rarely used property that creates extra complexity in
|
||||
ensuring it points to the correct instance. From a design
|
||||
perspective, a custom directive should be scoped to the element and
|
||||
data it is bound to and should not have access to the entire
|
||||
instance in the first place.
|
||||
|
||||
|
||||
|
||||
# [3.0.0-alpha.6](https://github.com/vuejs/vue-next/compare/v3.0.0-alpha.5...v3.0.0-alpha.6) (2020-02-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler-core:** should alias name in helperString ([#743](https://github.com/vuejs/vue-next/issues/743)) ([7b987d9](https://github.com/vuejs/vue-next/commit/7b987d9450fc7befcd0946a0d53991d27ed299ec)), closes [#740](https://github.com/vuejs/vue-next/issues/740)
|
||||
* **compiler-dom:** properly stringify class/style bindings when hoisting static strings ([1b9b235](https://github.com/vuejs/vue-next/commit/1b9b235663b75db040172d2ffbee1dd40b4db032))
|
||||
* **reactivity:** should trigger all effects when array length is mutated ([#754](https://github.com/vuejs/vue-next/issues/754)) ([5fac655](https://github.com/vuejs/vue-next/commit/5fac65589b4455b98fd4e2f9eb3754f0acde97bb))
|
||||
* **sfc:** inherit parent scopeId on child root ([#756](https://github.com/vuejs/vue-next/issues/756)) ([9547c2b](https://github.com/vuejs/vue-next/commit/9547c2b93d6d8f469314cfe055960746a3e3acbe))
|
||||
* **types:** improve ref typing, close [#759](https://github.com/vuejs/vue-next/issues/759) ([627b9df](https://github.com/vuejs/vue-next/commit/627b9df4a293ae18071009d9cac7a5e995d40716))
|
||||
* **types:** update setup binding unwrap types for 6b10f0c ([a840e7d](https://github.com/vuejs/vue-next/commit/a840e7ddf0b470b5da27b7b2b8b5fcf39a7197a2)), closes [#738](https://github.com/vuejs/vue-next/issues/738)
|
||||
|
||||
|
||||
### Code Refactoring
|
||||
|
||||
* preserve refs in reactive arrays ([775a7c2](https://github.com/vuejs/vue-next/commit/775a7c2b414ca44d4684badb29e8e80ff6b5d3dd)), closes [#737](https://github.com/vuejs/vue-next/issues/737)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **reactivity:** expose unref and shallowRef ([e9024bf](https://github.com/vuejs/vue-next/commit/e9024bf1b7456b9cf9b913c239502593364bc773))
|
||||
* **runtime-core:** add watchEffect API ([99a2e18](https://github.com/vuejs/vue-next/commit/99a2e18c9711d3d1f79f8c9c59212880efd058b9))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **effect:** optimize effect trigger for array length mutation ([#761](https://github.com/vuejs/vue-next/issues/761)) ([76c7f54](https://github.com/vuejs/vue-next/commit/76c7f5426919f9d29a303263bc54a1e42a66e94b))
|
||||
* **reactivity:** only trigger all effects on Array length mutation if new length is shorter than old length ([33622d6](https://github.com/vuejs/vue-next/commit/33622d63600ba0f18ba4dae97bda882c918b5f7d))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* **runtime-core:** replace `watch(fn, options?)` with `watchEffect`
|
||||
|
||||
The `watch(fn, options?)` signature has been replaced by the new
|
||||
`watchEffect` API, which has the same usage and behavior. `watch`
|
||||
now only supports the `watch(source, cb, options?)` signature.
|
||||
|
||||
* **reactivity:** reactive arrays no longer unwraps contained refs
|
||||
|
||||
When reactive arrays contain refs, especially a mix of refs and
|
||||
plain values, Array prototype methods will fail to function
|
||||
properly - e.g. sort() or reverse() will overwrite the ref's value
|
||||
instead of moving it (see #737).
|
||||
|
||||
Ensuring correct behavior for all possible Array methods while
|
||||
retaining the ref unwrapping behavior is exceedingly complicated; In
|
||||
addition, even if Vue handles the built-in methods internally, it
|
||||
would still break when the user attempts to use a 3rd party utility
|
||||
function (e.g. lodash) on a reactive array containing refs.
|
||||
|
||||
After this commit, similar to other collection types like Map and
|
||||
Set, Arrays will no longer automatically unwrap contained refs.
|
||||
|
||||
The usage of mixed refs and plain values in Arrays should be rare in
|
||||
practice. In cases where this is necessary, the user can create a
|
||||
computed property that performs the unwrapping.
|
||||
|
||||
|
||||
|
||||
# [3.0.0-alpha.5](https://github.com/vuejs/vue-next/compare/v3.0.0-alpha.4...v3.0.0-alpha.5) (2020-02-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler:** fix v-for fragment openBlock argument ([12fcf9a](https://github.com/vuejs/vue-next/commit/12fcf9ab953acdbb8706b549c7e63f69482a495a))
|
||||
* **compiler-core:** fix keep-alive when used in templates ([ade07c6](https://github.com/vuejs/vue-next/commit/ade07c64a1f98c0958e80db0458c699c21998f64)), closes [#715](https://github.com/vuejs/vue-next/issues/715)
|
||||
* **compiler-core:** only check is prop on `<component>` ([78c4f32](https://github.com/vuejs/vue-next/commit/78c4f321cd0902a117c599ac705dda294fa198ed))
|
||||
* **compiler-core:** relax error on unknown entities ([730d329](https://github.com/vuejs/vue-next/commit/730d329f794caf1ea2cc47628f8d74ef2d07f96e)), closes [#663](https://github.com/vuejs/vue-next/issues/663)
|
||||
* **compiler-core:** should apply text transform to if branches ([e0f3c6b](https://github.com/vuejs/vue-next/commit/e0f3c6b352ab35adcad779ef0ac9670acf3d7b37)), closes [#725](https://github.com/vuejs/vue-next/issues/725)
|
||||
* **compiler-core:** should not hoist element with cached + merged event handlers ([5455e8e](https://github.com/vuejs/vue-next/commit/5455e8e69a59cd1ff72330b1aed9c8e6aedc4b36))
|
||||
* **compiler-dom:** fix duplicated transforms ([9e51297](https://github.com/vuejs/vue-next/commit/9e51297702f975ced1cfebad9a46afc46f0593bb))
|
||||
* **compiler-sfc:** handle empty nodes with src attribute ([#695](https://github.com/vuejs/vue-next/issues/695)) ([2d56dfd](https://github.com/vuejs/vue-next/commit/2d56dfdc4fcf824bba4c0166ca5471258c4f883b))
|
||||
* **compiler-ssr:** import helpers from correct packages ([8f6b669](https://github.com/vuejs/vue-next/commit/8f6b6690a2011846446804267ec49073996c3800))
|
||||
* **computed:** support arrow function usage for computed option ([2fb7a63](https://github.com/vuejs/vue-next/commit/2fb7a63943d9d995248cb6d2d4fb5f22ff2ac000)), closes [#733](https://github.com/vuejs/vue-next/issues/733)
|
||||
* **reactivity:** avoid cross-component dependency leaks in setup() ([d9d63f2](https://github.com/vuejs/vue-next/commit/d9d63f21b1e6f99f2fb63d736501095b131e5ad9))
|
||||
* **reactivity:** effect should handle self dependency mutations ([e8e6772](https://github.com/vuejs/vue-next/commit/e8e67729cb7649d736be233b2a5e00768dd6f4ba))
|
||||
* **reactivity:** trigger iteration effect on Map.set ([e1c9153](https://github.com/vuejs/vue-next/commit/e1c9153b9ed71f9b2e1ad4f9018c51d239e7dcd0)), closes [#709](https://github.com/vuejs/vue-next/issues/709)
|
||||
* **runtime-core:** ensure renderCache always exists ([8383e54](https://github.com/vuejs/vue-next/commit/8383e5450e4f9679ac8a284f1c3960e3ee5b5211))
|
||||
* **runtime-core:** fix keep-alive tree-shaking ([5b43764](https://github.com/vuejs/vue-next/commit/5b43764eacb59ff6ebba3195a55af4ac0cf253bb))
|
||||
* **runtime-core:** fix ShapeFlags tree shaking ([0f67aa7](https://github.com/vuejs/vue-next/commit/0f67aa7da50d6ffc543754a42f1e677af11f9173))
|
||||
* **runtime-core:** handle component updates with only class/style bindings ([35d91f4](https://github.com/vuejs/vue-next/commit/35d91f4e18ccb72cbf39a86fe8f39060f0bf075e))
|
||||
* **runtime-core:** render context set should not unwrap reactive values ([27fbfbd](https://github.com/vuejs/vue-next/commit/27fbfbdb8beffc96134c931425f33178c23a72db))
|
||||
* **runtime-core:** rework vnode hooks handling ([cfadb98](https://github.com/vuejs/vue-next/commit/cfadb98011e188114bb822ee6f678cd09ddac7e3)), closes [#684](https://github.com/vuejs/vue-next/issues/684)
|
||||
* **runtime-core:** should not return early on text patchFlag ([778f3a5](https://github.com/vuejs/vue-next/commit/778f3a5e886a1a1136bc8b00b849370d7c4041be))
|
||||
* **runtime-core/scheduler:** avoid duplicate updates of child component ([8a87074](https://github.com/vuejs/vue-next/commit/8a87074df013fdbb0e88f34074c2605e4af2937c))
|
||||
* **runtime-core/scheduler:** invalidate job ([#717](https://github.com/vuejs/vue-next/issues/717)) ([fe9da2d](https://github.com/vuejs/vue-next/commit/fe9da2d0e4f9b338252b1b62941ee9ead71f0346))
|
||||
* **runtime-core/watch:** trigger watcher with undefined as initial value ([#687](https://github.com/vuejs/vue-next/issues/687)) ([5742a0b](https://github.com/vuejs/vue-next/commit/5742a0b826fe77d2310acb530667adb758822f66)), closes [#683](https://github.com/vuejs/vue-next/issues/683)
|
||||
* **runtime-dom/ssr:** properly handle xlink and boolean attributes ([e6e2c58](https://github.com/vuejs/vue-next/commit/e6e2c58234cab46fa530c383c0f7ae1cb3494da3))
|
||||
* **ssr:** avoid hard-coded ssr checks in cjs builds ([bc07e95](https://github.com/vuejs/vue-next/commit/bc07e95ca84686bfa43798a444a3220581b183d8))
|
||||
* **ssr:** fix class/style rendering + ssrRenderComponent export name ([688ad92](https://github.com/vuejs/vue-next/commit/688ad9239105625f7b63ac43181dfb2e9d1d4720))
|
||||
* **ssr:** render components returning render function from setup ([#720](https://github.com/vuejs/vue-next/issues/720)) ([4669215](https://github.com/vuejs/vue-next/commit/4669215ca2f82d90a1bd730613259f3167e199cd))
|
||||
* **transition-group:** handle multiple move-classes ([#679](https://github.com/vuejs/vue-next/issues/679)) ([5495c70](https://github.com/vuejs/vue-next/commit/5495c70c4a3f740ef4ac575ffee5466ca747cca1)), closes [#678](https://github.com/vuejs/vue-next/issues/678)
|
||||
* **types:** app.component should accept defineComponent return type ([57ee5df](https://github.com/vuejs/vue-next/commit/57ee5df364f03816e548f4f3bf05edc7a089c362)), closes [#730](https://github.com/vuejs/vue-next/issues/730)
|
||||
* **types:** ensure correct oldValue typing based on lazy option ([c6a9787](https://github.com/vuejs/vue-next/commit/c6a9787941ca99877d268182a5bb57fcf8b80b75)), closes [#719](https://github.com/vuejs/vue-next/issues/719)
|
||||
* **v-on:** transform click.right and click.middle modifiers ([028f748](https://github.com/vuejs/vue-next/commit/028f748c32f80842be39897fdacc37f6700f00a7)), closes [#735](https://github.com/vuejs/vue-next/issues/735)
|
||||
* remove effect from public API ([4bc4cb9](https://github.com/vuejs/vue-next/commit/4bc4cb970f7a65177948c5d817bb43ecb0324636)), closes [#712](https://github.com/vuejs/vue-next/issues/712)
|
||||
* **v-model:** should use dynamic directive on input with dynamic v-bind ([1f2de9e](https://github.com/vuejs/vue-next/commit/1f2de9e232409b09c97b67d0824d1450beed6eb1))
|
||||
|
||||
|
||||
### Code Refactoring
|
||||
|
||||
* **watch:** adjust watch API behavior ([9571ede](https://github.com/vuejs/vue-next/commit/9571ede84bb6949e13c25807cc8f016ace29dc8a))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **compiler:** mark hoisted trees with patchFlag ([175f8aa](https://github.com/vuejs/vue-next/commit/175f8aae8d009e044e3674f7647bf1397f3a794a))
|
||||
* **compiler:** warn invalid children for transition and keep-alive ([4cc39e1](https://github.com/vuejs/vue-next/commit/4cc39e14a297f42230f5aac5ec08e3c98902b98d))
|
||||
* **compiler-core:** support mode: cjs in codegen ([04da2a8](https://github.com/vuejs/vue-next/commit/04da2a82e8fbde2b60b2392bc4bdcc5e61113202))
|
||||
* **compiler-core/v-on:** support [@vnode-xxx](https://github.com/vnode-xxx) usage for vnode hooks ([571ed42](https://github.com/vuejs/vue-next/commit/571ed4226be618dcc9f95e4c2da8d82d7d2f7750))
|
||||
* **compiler-dom:** handle constant expressions when stringifying static content ([8b7c162](https://github.com/vuejs/vue-next/commit/8b7c162125cb72068727a76ede8afa2896251db0))
|
||||
* **compiler-dom/runtime-dom:** stringify eligible static trees ([27913e6](https://github.com/vuejs/vue-next/commit/27913e661ac551f580bd5fd42b49fe55cbe8dbb8))
|
||||
* **reactivity:** add shallowReactive function ([#689](https://github.com/vuejs/vue-next/issues/689)) ([7f38c1e](https://github.com/vuejs/vue-next/commit/7f38c1e0ff5a7591f67ed21aa3a2944db2e72a27))
|
||||
* **runtime-core/reactivity:** expose shallowReactive ([#711](https://github.com/vuejs/vue-next/issues/711)) ([21944c4](https://github.com/vuejs/vue-next/commit/21944c4a42a65f20245794fa5f07add579b7121f))
|
||||
* **server-renderer:** support on-the-fly template compilation ([#707](https://github.com/vuejs/vue-next/issues/707)) ([6d10a6c](https://github.com/vuejs/vue-next/commit/6d10a6c77242aec98103f15d6cb672ba63c18abf))
|
||||
* **ssr:** render portals ([#714](https://github.com/vuejs/vue-next/issues/714)) ([e495fa4](https://github.com/vuejs/vue-next/commit/e495fa4a1872d03ed59252e7ed5dd2b708adb7ae))
|
||||
* **ssr:** support portal hydration ([70dc3e3](https://github.com/vuejs/vue-next/commit/70dc3e3ae74f08d53243e6f078794c16f359e272))
|
||||
* **ssr:** useSSRContext ([fd03149](https://github.com/vuejs/vue-next/commit/fd031490fb89b7c0d1d478b586151a24324101a3))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* prevent renderer hot functions being inlined by minifiers ([629ee75](https://github.com/vuejs/vue-next/commit/629ee75588fc2ca4ab2b3786046f788d3547b6bc))
|
||||
* **reactivity:** better computed tracking ([#710](https://github.com/vuejs/vue-next/issues/710)) ([8874b21](https://github.com/vuejs/vue-next/commit/8874b21a7e2383a8bb6c15a7095c1853aa5ae705))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* **watch:** `watch` behavior has been adjusted.
|
||||
|
||||
- When using the `watch(source, callback, options?)` signature, the
|
||||
callback now fires lazily by default (consistent with 2.x
|
||||
behavior).
|
||||
|
||||
Note that the `watch(effect, options?)` signature is still eager,
|
||||
since it must invoke the `effect` immediately to collect
|
||||
dependencies.
|
||||
|
||||
- The `lazy` option has been replaced by the opposite `immediate`
|
||||
option, which defaults to `false`. (It's ignored when using the
|
||||
effect signature)
|
||||
|
||||
- Due to the above changes, the `watch` option in Options API now
|
||||
behaves exactly the same as 2.x.
|
||||
|
||||
- When using the effect signature or `{ immediate: true }`, the
|
||||
initial execution is now performed synchronously instead of
|
||||
deferred until the component is mounted. This is necessary for
|
||||
certain use cases to work properly with `async setup()` and
|
||||
Suspense.
|
||||
|
||||
The side effect of this is the immediate watcher invocation will
|
||||
no longer have access to the mounted DOM. However, the watcher can
|
||||
be initiated inside `onMounted` to retain previous behavior.
|
||||
|
||||
|
||||
|
||||
# [3.0.0-alpha.4](https://github.com/vuejs/vue-next/compare/v3.0.0-alpha.3...v3.0.0-alpha.4) (2020-01-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **reactivity:** Array methods relying on identity should work with raw values ([aefb7d2](https://github.com/vuejs/vue-next/commit/aefb7d282ed716923ca1a288a63a83a94af87ebc))
|
||||
* **runtime-core:** instance should not expose non-declared props ([2884831](https://github.com/vuejs/vue-next/commit/2884831065e16ccf5bd3ae1ee95116803ee3b18c))
|
||||
* **runtime-dom:** should not access document in non-browser env ([48152bc](https://github.com/vuejs/vue-next/commit/48152bc88ea817ae23e2987dce99d64b426366c1)), closes [#657](https://github.com/vuejs/vue-next/issues/657)
|
||||
* **v-model/emit:** update:camelCase events should trigger kebab case equivalent ([2837ce8](https://github.com/vuejs/vue-next/commit/2837ce842856d51dfbb55e3fa4a36a352446fb54)), closes [#656](https://github.com/vuejs/vue-next/issues/656)
|
||||
|
||||
|
||||
### Code Refactoring
|
||||
|
||||
* adjust `createApp` related API signatures ([c07751f](https://github.com/vuejs/vue-next/commit/c07751fd3605f301dc0f02fd2a48acc7ba7a0397))
|
||||
* remove implicit reactive() call on renderContext ([6b10f0c](https://github.com/vuejs/vue-next/commit/6b10f0cd1da942c1d96746672b5f595df7d125b5))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **ssr:** avoid unnecessary async overhead ([297282a](https://github.com/vuejs/vue-next/commit/297282a81259289bfed207d0c9393337aea70117))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* object returned from `setup()` are no longer implicitly
|
||||
passed to `reactive()`.
|
||||
|
||||
The renderContext is the object returned by `setup()` (or a new object
|
||||
if no setup() is present). Before this change, it was implicitly passed
|
||||
to `reactive()` for ref unwrapping. But this has the side effect of
|
||||
unnecessary deep reactive conversion on properties that should not be
|
||||
made reactive (e.g. computed return values and injected non-reactive
|
||||
objects), and can lead to performance issues.
|
||||
|
||||
This change removes the `reactive()` call and instead performs a
|
||||
shallow ref unwrapping at the render proxy level. The breaking part is
|
||||
when the user returns an object with a plain property from `setup()`,
|
||||
e.g. `return { count: 0 }`, this property will no longer trigger
|
||||
updates when mutated by a in-template event handler. Instead, explicit
|
||||
refs are required.
|
||||
|
||||
This also means that any objects not explicitly made reactive in
|
||||
`setup()` will remain non-reactive. This can be desirable when
|
||||
exposing heavy external stateful objects on `this`.
|
||||
* `createApp` API has been adjusted.
|
||||
|
||||
- `createApp()` now accepts the root component, and optionally a props
|
||||
object to pass to the root component.
|
||||
- `app.mount()` now accepts a single argument (the root container)
|
||||
- `app.unmount()` no longer requires arguments.
|
||||
|
||||
New behavior looks like the following:
|
||||
|
||||
``` js
|
||||
const app = createApp(RootComponent)
|
||||
app.mount('#app')
|
||||
app.unmount()
|
||||
```
|
||||
|
||||
|
||||
|
||||
# [3.0.0-alpha.3](https://github.com/vuejs/vue-next/compare/v3.0.0-alpha.2...v3.0.0-alpha.3) (2020-01-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Suspense should include into dynamic children ([#653](https://github.com/vuejs/vue-next/issues/653)) ([ec63623](https://github.com/vuejs/vue-next/commit/ec63623fe8d395e1cd759f27b90b1ccc1b616931)), closes [#649](https://github.com/vuejs/vue-next/issues/649)
|
||||
* **compiler-core:** avoid override user keys when injecting branch key ([#630](https://github.com/vuejs/vue-next/issues/630)) ([aca2c2a](https://github.com/vuejs/vue-next/commit/aca2c2a81e2793befce516378a02afd1e4da3d3d))
|
||||
* **compiler-core:** force `<svg>` into blocks for correct runtime isSVG ([f2ac28b](https://github.com/vuejs/vue-next/commit/f2ac28b31e9f1e8ebcd68ca9a1e8ea29653b0916))
|
||||
* **compiler-sfc:** only transform relative asset URLs ([#628](https://github.com/vuejs/vue-next/issues/628)) ([c71ca35](https://github.com/vuejs/vue-next/commit/c71ca354b9368135b55676c5817cebffaf3fd9c5))
|
||||
* **dom:** fix `<svg>` and `<foreignObject>` mount and updates ([4f06eeb](https://github.com/vuejs/vue-next/commit/4f06eebc1c2a29d0e4165c6e87f849732ec2cd0f))
|
||||
* **runtime-core:** condition for parent node check should be any different nodes ([c35fea3](https://github.com/vuejs/vue-next/commit/c35fea3d608acbb571ace6693284061e1cadf7ba)), closes [#622](https://github.com/vuejs/vue-next/issues/622)
|
||||
* **runtime-core:** isSVG check should also apply for patch branch ([035b656](https://github.com/vuejs/vue-next/commit/035b6560f7eb64ce940ed0d06e19086ad9a3890f)), closes [#639](https://github.com/vuejs/vue-next/issues/639)
|
||||
* **runtime-core:** should not warn unused attrs when accessed via setup context ([751d838](https://github.com/vuejs/vue-next/commit/751d838fb963e580a40df2d84840ba2198480185)), closes [#625](https://github.com/vuejs/vue-next/issues/625)
|
||||
* **transition:** handle multiple transition classes ([#638](https://github.com/vuejs/vue-next/issues/638)) ([#645](https://github.com/vuejs/vue-next/issues/645)) ([98d50d8](https://github.com/vuejs/vue-next/commit/98d50d874dcb32a246216b936e442e5b95ab4825))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **runtime-core:** emit now returns array of return values from all triggered handlers ([e81c8a3](https://github.com/vuejs/vue-next/commit/e81c8a32c7b66211cbaecffa93efd4629ec45ad9)), closes [#635](https://github.com/vuejs/vue-next/issues/635)
|
||||
* **runtime-core:** support app.unmount(container) ([#601](https://github.com/vuejs/vue-next/issues/601)) ([04ac6c4](https://github.com/vuejs/vue-next/commit/04ac6c467a4122877c204d7494c86f89498d2dc6)), closes [#593](https://github.com/vuejs/vue-next/issues/593)
|
||||
|
||||
|
||||
|
||||
# [3.0.0-alpha.2](https://github.com/vuejs/vue-next/compare/v3.0.0-alpha.1...v3.0.0-alpha.2) (2020-01-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler/v-on:** handle multiple statements in v-on handler (close [#572](https://github.com/vuejs/vue-next/issues/572)) ([137893a](https://github.com/vuejs/vue-next/commit/137893a4fdd3d2b901adca31e30d916df925b108))
|
||||
* **compiler/v-slot:** handle implicit default slot mixed with named slots ([2ac4b72](https://github.com/vuejs/vue-next/commit/2ac4b723e010082488b5be64af73e41c9677a28d))
|
||||
* **reactivity:** should delete observe value ([#598](https://github.com/vuejs/vue-next/issues/598)) ([63a6563](https://github.com/vuejs/vue-next/commit/63a656310676e3927b2e57d813fd6300c0a42590)), closes [#597](https://github.com/vuejs/vue-next/issues/597)
|
||||
* **runtime-core:** allow classes to be passed as plugins ([#588](https://github.com/vuejs/vue-next/issues/588)) ([8f616a8](https://github.com/vuejs/vue-next/commit/8f616a89c580bc211540d5e4d60488ff24d024cc))
|
||||
* **runtime-core:** should preserve props casing when component has no declared props ([bb6a346](https://github.com/vuejs/vue-next/commit/bb6a346996ce0bf05596c605ba5ddbe0743ef84b)), closes [#583](https://github.com/vuejs/vue-next/issues/583)
|
||||
* **runtime-core/renderer:** fix v-if toggle inside blocks ([2e9726e](https://github.com/vuejs/vue-next/commit/2e9726e6a219d546cd28e4ed42be64719708f047)), closes [#604](https://github.com/vuejs/vue-next/issues/604) [#607](https://github.com/vuejs/vue-next/issues/607)
|
||||
* **runtime-core/vnode:** should not render boolean values in vnode children (close [#574](https://github.com/vuejs/vue-next/issues/574)) ([84dc5a6](https://github.com/vuejs/vue-next/commit/84dc5a686275528733977ea1570e0a892ba3e177))
|
||||
* **types:** components options should accept components defined with defineComponent ([#602](https://github.com/vuejs/vue-next/issues/602)) ([74baea1](https://github.com/vuejs/vue-next/commit/74baea108aa93377c4959f9a6b8bc8f9548700ba))
|
||||
* **watch:** remove recorded effect on manual stop ([#590](https://github.com/vuejs/vue-next/issues/590)) ([453e688](https://github.com/vuejs/vue-next/commit/453e6889da22e7224b638261a32438bdf5c62e41))
|
||||
|
||||
|
||||
|
||||
# [3.0.0-alpha.1](https://github.com/vuejs/vue-next/compare/v3.0.0-alpha.0...v3.0.0-alpha.1) (2020-01-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **runtime-core:** pass options to plugins ([#561](https://github.com/vuejs/vue-next/issues/561)) ([4d20981](https://github.com/vuejs/vue-next/commit/4d20981eb069b20e1627916b977aedb2d68eca86))
|
||||
* **sfc:** treat custom block content as raw text ([d6275a3](https://github.com/vuejs/vue-next/commit/d6275a3c310e6e9426f897afe35ff6cdb125c023))
|
||||
* mounting new children ([7d436ab](https://github.com/vuejs/vue-next/commit/7d436ab59a30562a049e199ae579df7ac8066829))
|
||||
* **core:** clone mounted hoisted vnodes on patch ([47a6a84](https://github.com/vuejs/vue-next/commit/47a6a846311203fa59584486265f5da387afa51d))
|
||||
* **fragment:** perform direct remove when removing fragments ([2fdb499](https://github.com/vuejs/vue-next/commit/2fdb499bd96b4d1a8a7a1964d59e8dc5dacd9d22))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **hmr:** root instance reload ([eda495e](https://github.com/vuejs/vue-next/commit/eda495efd824f17095728a4d2a6db85ca874e5ca))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **compiler-core:** simplify `advancePositionWithMutation` ([#564](https://github.com/vuejs/vue-next/issues/564)) ([ad2a0bd](https://github.com/vuejs/vue-next/commit/ad2a0bde988de743d4abc62b681b6a4888545a51))
|
||||
|
||||
|
||||
|
||||
# [3.0.0-alpha.0](https://github.com/vuejs/vue-next/compare/a8522cf48c09efbb2063f129cf1bea0dae09f10a...v3.0.0-alpha.0) (2019-12-20)
|
||||
|
||||
For changes between 2.x and 3.0 up to this release, please refer to merged RFCs [here](https://github.com/vuejs/rfcs/pulls?q=is%3Apr+is%3Amerged+label%3A3.x).
|
28
README.md
28
README.md
@ -1,32 +1,14 @@
|
||||
# vue-next [![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
|
||||
|
||||
|
@ -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/']
|
||||
}
|
||||
|
43
package.json
43
package.json
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
@ -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 */))
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,91 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`scopeId compiler support should push scopeId for hoisted nodes 1`] = `
|
||||
"import { createVNode as _createVNode, toDisplayString as _toDisplayString, createTextVNode as _createTextVNode, openBlock as _openBlock, createBlock as _createBlock, withScopeId as _withScopeId, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \\"vue\\"
|
||||
const _withId = _withScopeId(\\"test\\")
|
||||
|
||||
_pushScopeId(\\"test\\")
|
||||
const _hoisted_1 = _createVNode(\\"div\\", null, \\"hello\\", -1 /* HOISTED */)
|
||||
const _hoisted_2 = _createVNode(\\"div\\", null, \\"world\\", -1 /* HOISTED */)
|
||||
_popScopeId()
|
||||
|
||||
export const render = _withId(function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createBlock(\\"div\\", null, [
|
||||
_hoisted_1,
|
||||
_createTextVNode(_toDisplayString(_ctx.foo), 1 /* TEXT */),
|
||||
_hoisted_2
|
||||
]))
|
||||
})"
|
||||
`;
|
||||
|
||||
exports[`scopeId compiler support should wrap default slot 1`] = `
|
||||
"import { createVNode as _createVNode, resolveComponent as _resolveComponent, withCtx as _withCtx, openBlock as _openBlock, createBlock as _createBlock, withScopeId as _withScopeId } from \\"vue\\"
|
||||
const _withId = _withScopeId(\\"test\\")
|
||||
|
||||
export const render = _withId(function render(_ctx, _cache) {
|
||||
const _component_Child = _resolveComponent(\\"Child\\")
|
||||
|
||||
return (_openBlock(), _createBlock(_component_Child, null, {
|
||||
default: _withId(() => [
|
||||
_createVNode(\\"div\\")
|
||||
]),
|
||||
_: 1
|
||||
}))
|
||||
})"
|
||||
`;
|
||||
|
||||
exports[`scopeId compiler support should wrap dynamic slots 1`] = `
|
||||
"import { createVNode as _createVNode, resolveComponent as _resolveComponent, withCtx as _withCtx, renderList as _renderList, createSlots as _createSlots, openBlock as _openBlock, createBlock as _createBlock, withScopeId as _withScopeId } from \\"vue\\"
|
||||
const _withId = _withScopeId(\\"test\\")
|
||||
|
||||
export const render = _withId(function render(_ctx, _cache) {
|
||||
const _component_Child = _resolveComponent(\\"Child\\")
|
||||
|
||||
return (_openBlock(), _createBlock(_component_Child, null, _createSlots({ _: 1 }, [
|
||||
(_ctx.ok)
|
||||
? {
|
||||
name: \\"foo\\",
|
||||
fn: _withId(() => [
|
||||
_createVNode(\\"div\\")
|
||||
])
|
||||
}
|
||||
: undefined,
|
||||
_renderList(_ctx.list, (i) => {
|
||||
return {
|
||||
name: i,
|
||||
fn: _withId(() => [
|
||||
_createVNode(\\"div\\")
|
||||
])
|
||||
}
|
||||
})
|
||||
]), 1024 /* DYNAMIC_SLOTS */))
|
||||
})"
|
||||
`;
|
||||
|
||||
exports[`scopeId compiler support should wrap named slots 1`] = `
|
||||
"import { toDisplayString as _toDisplayString, createTextVNode as _createTextVNode, createVNode as _createVNode, resolveComponent as _resolveComponent, withCtx as _withCtx, openBlock as _openBlock, createBlock as _createBlock, withScopeId as _withScopeId } from \\"vue\\"
|
||||
const _withId = _withScopeId(\\"test\\")
|
||||
|
||||
export const render = _withId(function render(_ctx, _cache) {
|
||||
const _component_Child = _resolveComponent(\\"Child\\")
|
||||
|
||||
return (_openBlock(), _createBlock(_component_Child, null, {
|
||||
foo: _withId(({ msg }) => [
|
||||
_createTextVNode(_toDisplayString(msg), 1 /* TEXT */)
|
||||
]),
|
||||
bar: _withId(() => [
|
||||
_createVNode(\\"div\\")
|
||||
]),
|
||||
_: 1
|
||||
}))
|
||||
})"
|
||||
`;
|
||||
|
||||
exports[`scopeId compiler support should wrap render function 1`] = `
|
||||
"import { createVNode as _createVNode, openBlock as _openBlock, createBlock as _createBlock, withScopeId as _withScopeId } from \\"vue\\"
|
||||
const _withId = _withScopeId(\\"test\\")
|
||||
|
||||
export const render = _withId(function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createBlock(\\"div\\"))
|
||||
})"
|
||||
`;
|
@ -9,20 +9,28 @@ import {
|
||||
createArrayExpression,
|
||||
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]
|
||||
])
|
||||
"
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -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`))
|
||||
|
@ -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('&ersand;', {
|
||||
const ast = baseParse('&ersand;', {
|
||||
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="&ersand;" b="&ersand;" c="&!"></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('†', { onError: spy })
|
||||
const ast = baseParse('†', { 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('&∪︀', {
|
||||
const ast: any = baseParse('&∪︀', {
|
||||
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) {
|
||||
|
97
packages/compiler-core/__tests__/scopeId.spec.ts
Normal file
97
packages/compiler-core/__tests__/scopeId.spec.ts
Normal file
@ -0,0 +1,97 @@
|
||||
import { baseCompile } from '../src/compile'
|
||||
import {
|
||||
WITH_SCOPE_ID,
|
||||
PUSH_SCOPE_ID,
|
||||
POP_SCOPE_ID
|
||||
} from '../src/runtimeHelpers'
|
||||
import { PatchFlags } from '@vue/shared'
|
||||
import { genFlagText } from './testUtils'
|
||||
|
||||
describe('scopeId compiler support', () => {
|
||||
test('should only work in module mode', () => {
|
||||
expect(() => {
|
||||
baseCompile(``, { scopeId: 'test' })
|
||||
}).toThrow(`"scopeId" option is only supported in module mode`)
|
||||
})
|
||||
|
||||
test('should wrap render function', () => {
|
||||
const { ast, code } = baseCompile(`<div/>`, {
|
||||
mode: 'module',
|
||||
scopeId: 'test'
|
||||
})
|
||||
expect(ast.helpers).toContain(WITH_SCOPE_ID)
|
||||
expect(code).toMatch(`const _withId = _withScopeId("test")`)
|
||||
expect(code).toMatch(`export const render = _withId(function render(`)
|
||||
expect(code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('should wrap default slot', () => {
|
||||
const { code } = baseCompile(`<Child><div/></Child>`, {
|
||||
mode: 'module',
|
||||
scopeId: 'test'
|
||||
})
|
||||
expect(code).toMatch(`default: _withId(() => [`)
|
||||
expect(code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('should wrap named slots', () => {
|
||||
const { code } = baseCompile(
|
||||
`<Child>
|
||||
<template #foo="{ msg }">{{ msg }}</template>
|
||||
<template #bar><div/></template>
|
||||
</Child>
|
||||
`,
|
||||
{
|
||||
mode: 'module',
|
||||
scopeId: 'test'
|
||||
}
|
||||
)
|
||||
expect(code).toMatch(`foo: _withId(({ msg }) => [`)
|
||||
expect(code).toMatch(`bar: _withId(() => [`)
|
||||
expect(code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('should wrap dynamic slots', () => {
|
||||
const { code } = baseCompile(
|
||||
`<Child>
|
||||
<template #foo v-if="ok"><div/></template>
|
||||
<template v-for="i in list" #[i]><div/></template>
|
||||
</Child>
|
||||
`,
|
||||
{
|
||||
mode: 'module',
|
||||
scopeId: 'test'
|
||||
}
|
||||
)
|
||||
expect(code).toMatch(/name: "foo",\s+fn: _withId\(/)
|
||||
expect(code).toMatch(/name: i,\s+fn: _withId\(/)
|
||||
expect(code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('should push scopeId for hoisted nodes', () => {
|
||||
const { ast, code } = baseCompile(
|
||||
`<div><div>hello</div>{{ foo }}<div>world</div></div>`,
|
||||
{
|
||||
mode: 'module',
|
||||
scopeId: 'test',
|
||||
hoistStatic: true
|
||||
}
|
||||
)
|
||||
expect(ast.helpers).toContain(PUSH_SCOPE_ID)
|
||||
expect(ast.helpers).toContain(POP_SCOPE_ID)
|
||||
expect(ast.hoists.length).toBe(2)
|
||||
expect(code).toMatch(
|
||||
[
|
||||
`_pushScopeId("test")`,
|
||||
`const _hoisted_1 = _createVNode("div", null, "hello", ${genFlagText(
|
||||
PatchFlags.HOISTED
|
||||
)})`,
|
||||
`const _hoisted_2 = _createVNode("div", null, "world", ${genFlagText(
|
||||
PatchFlags.HOISTED
|
||||
)})`,
|
||||
`_popScopeId()`
|
||||
].join('\n')
|
||||
)
|
||||
expect(code).toMatchSnapshot()
|
||||
})
|
||||
})
|
@ -4,9 +4,8 @@ import {
|
||||
locStub,
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
])
|
||||
)
|
||||
)
|
||||
})
|
||||
})
|
||||
|
@ -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)
|
||||
]))
|
||||
}
|
||||
}"
|
||||
|
@ -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)
|
||||
}"
|
||||
`;
|
||||
|
@ -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 */))
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
@ -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)
|
||||
|
@ -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),
|
||||
|
@ -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
|
||||
}))
|
||||
}"
|
||||
`;
|
||||
|
@ -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,
|
||||
|
@ -0,0 +1,24 @@
|
||||
import {
|
||||
baseParse as parse,
|
||||
transform,
|
||||
ElementNode,
|
||||
noopDirectiveTransform,
|
||||
VNodeCall
|
||||
} from '../../src'
|
||||
import { transformElement } from '../../src/transforms/transformElement'
|
||||
|
||||
describe('compiler: noop directive transform', () => {
|
||||
test('should add no props to DOM', () => {
|
||||
const ast = parse(`<div v-noop/>`)
|
||||
transform(ast, {
|
||||
nodeTransforms: [transformElement],
|
||||
directiveTransforms: {
|
||||
noop: noopDirectiveTransform
|
||||
}
|
||||
})
|
||||
const node = ast.children[0] as ElementNode
|
||||
// As v-noop adds no properties the codegen should be identical to
|
||||
// rendering a div with no props or reactive data (so just the tag as the arg)
|
||||
expect((node.codegenNode as VNodeCall).props).toBeUndefined()
|
||||
})
|
||||
})
|
@ -1,24 +1,28 @@
|
||||
import { CompilerOptions, parse, transform, ErrorCodes } from '../../src'
|
||||
import {
|
||||
CompilerOptions,
|
||||
baseParse as parse,
|
||||
transform,
|
||||
ErrorCodes
|
||||
} from '../../src'
|
||||
import {
|
||||
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
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -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` }]
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -1,6 +1,6 @@
|
||||
import {
|
||||
CompilerOptions,
|
||||
parse,
|
||||
baseParse as parse,
|
||||
transform,
|
||||
ElementNode,
|
||||
NodeTypes,
|
||||
|
@ -1,6 +1,6 @@
|
||||
import {
|
||||
CompilerOptions,
|
||||
parse,
|
||||
baseParse as parse,
|
||||
transform,
|
||||
NodeTypes,
|
||||
generate,
|
||||
|
@ -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` },
|
||||
|
@ -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()
|
||||
})
|
||||
|
@ -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')
|
||||
|
@ -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', () => {
|
||||
|
@ -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` }, `++`] },
|
||||
`)`
|
||||
]
|
||||
}
|
||||
})
|
||||
})
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
100
packages/compiler-core/src/compile.ts
Normal file
100
packages/compiler-core/src/compile.ts
Normal file
@ -0,0 +1,100 @@
|
||||
import { CompilerOptions } from './options'
|
||||
import { baseParse } from './parse'
|
||||
import { transform, NodeTransform, DirectiveTransform } from './transform'
|
||||
import { generate, CodegenResult } from './codegen'
|
||||
import { RootNode } from './ast'
|
||||
import { isString } from '@vue/shared'
|
||||
import { transformIf } from './transforms/vIf'
|
||||
import { transformFor } from './transforms/vFor'
|
||||
import { transformExpression } from './transforms/transformExpression'
|
||||
import { transformSlotOutlet } from './transforms/transformSlotOutlet'
|
||||
import { transformElement } from './transforms/transformElement'
|
||||
import { transformOn } from './transforms/vOn'
|
||||
import { transformBind } from './transforms/vBind'
|
||||
import { trackSlotScopes, trackVForSlotScopes } from './transforms/vSlot'
|
||||
import { transformText } from './transforms/transformText'
|
||||
import { transformOnce } from './transforms/vOnce'
|
||||
import { transformModel } from './transforms/vModel'
|
||||
import { defaultOnError, createCompilerError, ErrorCodes } from './errors'
|
||||
|
||||
export type TransformPreset = [
|
||||
NodeTransform[],
|
||||
Record<string, DirectiveTransform>
|
||||
]
|
||||
|
||||
export function getBaseTransformPreset(
|
||||
prefixIdentifiers?: boolean
|
||||
): TransformPreset {
|
||||
return [
|
||||
[
|
||||
transformOnce,
|
||||
transformIf,
|
||||
transformFor,
|
||||
...(!__BROWSER__ && prefixIdentifiers
|
||||
? [
|
||||
// order is important
|
||||
trackVForSlotScopes,
|
||||
transformExpression
|
||||
]
|
||||
: []),
|
||||
transformSlotOutlet,
|
||||
transformElement,
|
||||
trackSlotScopes,
|
||||
transformText
|
||||
],
|
||||
{
|
||||
on: transformOn,
|
||||
bind: transformBind,
|
||||
model: transformModel
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// we name it `baseCompile` so that higher order compilers like
|
||||
// @vue/compiler-dom can export `compile` while re-exporting everything else.
|
||||
export function baseCompile(
|
||||
template: string | RootNode,
|
||||
options: CompilerOptions = {}
|
||||
): CodegenResult {
|
||||
const onError = options.onError || defaultOnError
|
||||
const isModuleMode = options.mode === 'module'
|
||||
/* istanbul ignore if */
|
||||
if (__BROWSER__) {
|
||||
if (options.prefixIdentifiers === true) {
|
||||
onError(createCompilerError(ErrorCodes.X_PREFIX_ID_NOT_SUPPORTED))
|
||||
} else if (isModuleMode) {
|
||||
onError(createCompilerError(ErrorCodes.X_MODULE_MODE_NOT_SUPPORTED))
|
||||
}
|
||||
}
|
||||
|
||||
const prefixIdentifiers =
|
||||
!__BROWSER__ && (options.prefixIdentifiers === true || isModuleMode)
|
||||
if (!prefixIdentifiers && options.cacheHandlers) {
|
||||
onError(createCompilerError(ErrorCodes.X_CACHE_HANDLER_NOT_SUPPORTED))
|
||||
}
|
||||
if (options.scopeId && !isModuleMode) {
|
||||
onError(createCompilerError(ErrorCodes.X_SCOPE_ID_NOT_SUPPORTED))
|
||||
}
|
||||
|
||||
const ast = isString(template) ? baseParse(template, options) : template
|
||||
const [nodeTransforms, directiveTransforms] = getBaseTransformPreset(
|
||||
prefixIdentifiers
|
||||
)
|
||||
transform(ast, {
|
||||
...options,
|
||||
prefixIdentifiers,
|
||||
nodeTransforms: [
|
||||
...nodeTransforms,
|
||||
...(options.nodeTransforms || []) // user transforms
|
||||
],
|
||||
directiveTransforms: {
|
||||
...directiveTransforms,
|
||||
...(options.directiveTransforms || {}) // user transforms
|
||||
}
|
||||
})
|
||||
|
||||
return generate(ast, {
|
||||
...options,
|
||||
prefixIdentifiers
|
||||
})
|
||||
}
|
@ -16,11 +16,14 @@ export function defaultOnError(error: CompilerError) {
|
||||
export function createCompilerError<T extends number>(
|
||||
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.`
|
||||
}
|
||||
|
@ -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'
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -0,0 +1,3 @@
|
||||
import { DirectiveTransform } from '../transform'
|
||||
|
||||
export const noopDirectiveTransform: DirectiveTransform = () => ({ props: [] })
|
@ -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 + `]`
|
||||
}
|
||||
|
@ -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
|
||||
) &&
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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]} */`
|
||||
)
|
||||
|
@ -30,7 +30,6 @@ export const transformBind: DirectiveTransform = (dir, node, context) => {
|
||||
return {
|
||||
props: [
|
||||
createObjectProperty(arg!, exp || createSimpleExpression('', true, loc))
|
||||
],
|
||||
needRuntime: false
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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 }
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 */))
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -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]
|
||||
]))
|
||||
])
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
@ -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]
|
||||
]))
|
||||
])
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
@ -0,0 +1,124 @@
|
||||
import { compile, NodeTypes, CREATE_STATIC } from '../../src'
|
||||
import {
|
||||
stringifyStatic,
|
||||
StringifyThresholds
|
||||
} from '../../src/transforms/stringifyStatic'
|
||||
|
||||
describe('stringify static html', () => {
|
||||
function compileWithStringify(template: string) {
|
||||
return compile(template, {
|
||||
hoistStatic: true,
|
||||
prefixIdentifiers: true,
|
||||
transformHoist: stringifyStatic
|
||||
})
|
||||
}
|
||||
|
||||
function repeat(code: string, n: number): string {
|
||||
return new Array(n)
|
||||
.fill(0)
|
||||
.map(() => code)
|
||||
.join('')
|
||||
}
|
||||
|
||||
test('should bail on non-eligible static trees', () => {
|
||||
const { ast } = compileWithStringify(
|
||||
`<div><div><div>hello</div><div>hello</div></div></div>`
|
||||
)
|
||||
expect(ast.hoists.length).toBe(1)
|
||||
// should be a normal vnode call
|
||||
expect(ast.hoists[0].type).toBe(NodeTypes.VNODE_CALL)
|
||||
})
|
||||
|
||||
test('should work on eligible content (elements with binding > 5)', () => {
|
||||
const { ast } = compileWithStringify(
|
||||
`<div><div>${repeat(
|
||||
`<span class="foo"/>`,
|
||||
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
|
||||
)}</div></div>`
|
||||
)
|
||||
expect(ast.hoists.length).toBe(1)
|
||||
// should be optimized now
|
||||
expect(ast.hoists[0]).toMatchObject({
|
||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||
callee: CREATE_STATIC,
|
||||
arguments: [
|
||||
JSON.stringify(
|
||||
`<div>${repeat(
|
||||
`<span class="foo"></span>`,
|
||||
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
|
||||
)}</div>`
|
||||
)
|
||||
]
|
||||
})
|
||||
})
|
||||
|
||||
test('should work on eligible content (elements > 20)', () => {
|
||||
const { ast } = compileWithStringify(
|
||||
`<div><div>${repeat(
|
||||
`<span/>`,
|
||||
StringifyThresholds.NODE_COUNT
|
||||
)}</div></div>`
|
||||
)
|
||||
expect(ast.hoists.length).toBe(1)
|
||||
// should be optimized now
|
||||
expect(ast.hoists[0]).toMatchObject({
|
||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||
callee: CREATE_STATIC,
|
||||
arguments: [
|
||||
JSON.stringify(
|
||||
`<div>${repeat(
|
||||
`<span></span>`,
|
||||
StringifyThresholds.NODE_COUNT
|
||||
)}</div>`
|
||||
)
|
||||
]
|
||||
})
|
||||
})
|
||||
|
||||
test('serliazing constant bindings', () => {
|
||||
const { ast } = compileWithStringify(
|
||||
`<div><div :style="{ color: 'red' }">${repeat(
|
||||
`<span :class="[{ foo: true }, { bar: true }]">{{ 1 }} + {{ false }}</span>`,
|
||||
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
|
||||
)}</div></div>`
|
||||
)
|
||||
expect(ast.hoists.length).toBe(1)
|
||||
// should be optimized now
|
||||
expect(ast.hoists[0]).toMatchObject({
|
||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||
callee: CREATE_STATIC,
|
||||
arguments: [
|
||||
JSON.stringify(
|
||||
`<div style="color:red;">${repeat(
|
||||
`<span class="foo bar">1 + false</span>`,
|
||||
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
|
||||
)}</div>`
|
||||
)
|
||||
]
|
||||
})
|
||||
})
|
||||
|
||||
test('escape', () => {
|
||||
const { ast } = compileWithStringify(
|
||||
`<div><div>${repeat(
|
||||
`<span :class="'foo' + '>ar'">{{ 1 }} + {{ '<' }}</span>` +
|
||||
`<span>&</span>`,
|
||||
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
|
||||
)}</div></div>`
|
||||
)
|
||||
expect(ast.hoists.length).toBe(1)
|
||||
// should be optimized now
|
||||
expect(ast.hoists[0]).toMatchObject({
|
||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||
callee: CREATE_STATIC,
|
||||
arguments: [
|
||||
JSON.stringify(
|
||||
`<div>${repeat(
|
||||
`<span class="foo>ar">1 + <</span>` + `<span>&</span>`,
|
||||
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
|
||||
)}</div>`
|
||||
)
|
||||
]
|
||||
})
|
||||
})
|
||||
})
|
@ -1,10 +1,10 @@
|
||||
import {
|
||||
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()
|
||||
})
|
||||
})
|
||||
|
@ -1,30 +0,0 @@
|
||||
import {
|
||||
parse,
|
||||
transform,
|
||||
ElementNode,
|
||||
CallExpression
|
||||
} from '@vue/compiler-core'
|
||||
import { transformCloak } from '../../src/transforms/vCloak'
|
||||
import { transformElement } from '../../../compiler-core/src/transforms/transformElement'
|
||||
|
||||
function transformWithCloak(template: string) {
|
||||
const ast = parse(template)
|
||||
transform(ast, {
|
||||
nodeTransforms: [transformElement],
|
||||
directiveTransforms: {
|
||||
cloak: transformCloak
|
||||
}
|
||||
})
|
||||
return ast.children[0] as ElementNode
|
||||
}
|
||||
|
||||
describe('compiler: v-cloak transform', () => {
|
||||
test('should add no props to DOM', () => {
|
||||
const node = transformWithCloak(`<div v-cloak/>`)
|
||||
const codegenArgs = (node.codegenNode as CallExpression).arguments
|
||||
|
||||
// As v-cloak adds no properties the codegen should be identical to
|
||||
// rendering a div with no props or reactive data (so just the tag as the arg)
|
||||
expect(codegenArgs.length).toBe(1)
|
||||
})
|
||||
})
|
@ -1,5 +1,5 @@
|
||||
import {
|
||||
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"]`
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -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" />')
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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'
|
||||
|
@ -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"]`
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -0,0 +1,140 @@
|
||||
import { compile } from '../../src'
|
||||
|
||||
describe('compiler warnings', () => {
|
||||
describe('Transition', () => {
|
||||
function checkWarning(
|
||||
template: string,
|
||||
shouldWarn: boolean,
|
||||
message = `<Transition> expects exactly one child element or component.`
|
||||
) {
|
||||
const spy = jest.fn()
|
||||
compile(template.trim(), {
|
||||
hoistStatic: true,
|
||||
transformHoist: null,
|
||||
onError: err => {
|
||||
spy(err.message)
|
||||
}
|
||||
})
|
||||
|
||||
if (shouldWarn) expect(spy).toHaveBeenCalledWith(message)
|
||||
else expect(spy).not.toHaveBeenCalled()
|
||||
}
|
||||
|
||||
test('warns if multiple children', () => {
|
||||
checkWarning(
|
||||
`
|
||||
<transition>
|
||||
<div>hey</div>
|
||||
<div>hey</div>
|
||||
</transition>
|
||||
`,
|
||||
true
|
||||
)
|
||||
})
|
||||
|
||||
test('warns with v-for', () => {
|
||||
checkWarning(
|
||||
`
|
||||
<transition>
|
||||
<div v-for="i in items">hey</div>
|
||||
</transition>
|
||||
`,
|
||||
true
|
||||
)
|
||||
})
|
||||
|
||||
test('warns with multiple v-if + v-for', () => {
|
||||
checkWarning(
|
||||
`
|
||||
<transition>
|
||||
<div v-if="a" v-for="i in items">hey</div>
|
||||
<div v-else v-for="i in items">hey</div>
|
||||
</transition>
|
||||
`,
|
||||
true
|
||||
)
|
||||
})
|
||||
|
||||
test('warns with template v-if', () => {
|
||||
checkWarning(
|
||||
`
|
||||
<transition>
|
||||
<template v-if="ok"></template>
|
||||
</transition>
|
||||
`,
|
||||
true
|
||||
)
|
||||
})
|
||||
|
||||
test('warns with multiple templates', () => {
|
||||
checkWarning(
|
||||
`
|
||||
<transition>
|
||||
<template v-if="a"></template>
|
||||
<template v-else></template>
|
||||
</transition>
|
||||
`,
|
||||
true
|
||||
)
|
||||
})
|
||||
|
||||
test('warns if multiple children with v-if', () => {
|
||||
checkWarning(
|
||||
`
|
||||
<transition>
|
||||
<div v-if="one">hey</div>
|
||||
<div v-if="other">hey</div>
|
||||
</transition>
|
||||
`,
|
||||
true
|
||||
)
|
||||
})
|
||||
|
||||
test('does not warn with regular element', () => {
|
||||
checkWarning(
|
||||
`
|
||||
<transition>
|
||||
<div>hey</div>
|
||||
</transition>
|
||||
`,
|
||||
false
|
||||
)
|
||||
})
|
||||
|
||||
test('does not warn with one single v-if', () => {
|
||||
checkWarning(
|
||||
`
|
||||
<transition>
|
||||
<div v-if="a">hey</div>
|
||||
</transition>
|
||||
`,
|
||||
false
|
||||
)
|
||||
})
|
||||
|
||||
test('does not warn with v-if v-else-if v-else', () => {
|
||||
checkWarning(
|
||||
`
|
||||
<transition>
|
||||
<div v-if="a">hey</div>
|
||||
<div v-else-if="b">hey</div>
|
||||
<div v-else>hey</div>
|
||||
</transition>
|
||||
`,
|
||||
false
|
||||
)
|
||||
})
|
||||
|
||||
test('does not warn with v-if v-else', () => {
|
||||
checkWarning(
|
||||
`
|
||||
<transition>
|
||||
<div v-if="a">hey</div>
|
||||
<div v-else>hey</div>
|
||||
</transition>
|
||||
`,
|
||||
false
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
@ -1,18 +1,18 @@
|
||||
{
|
||||
"name": "@vue/compiler-dom",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
@ -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.`
|
||||
}
|
||||
|
@ -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'
|
||||
|
@ -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
|
||||
|
171
packages/compiler-dom/src/transforms/stringifyStatic.ts
Normal file
171
packages/compiler-dom/src/transforms/stringifyStatic.ts
Normal file
@ -0,0 +1,171 @@
|
||||
import {
|
||||
NodeTypes,
|
||||
ElementNode,
|
||||
TransformContext,
|
||||
TemplateChildNode,
|
||||
SimpleExpressionNode,
|
||||
createCallExpression,
|
||||
HoistTransform,
|
||||
CREATE_STATIC,
|
||||
ExpressionNode
|
||||
} from '@vue/compiler-core'
|
||||
import {
|
||||
isVoidTag,
|
||||
isString,
|
||||
isSymbol,
|
||||
escapeHtml,
|
||||
toDisplayString,
|
||||
normalizeClass,
|
||||
normalizeStyle,
|
||||
stringifyStyle
|
||||
} from '@vue/shared'
|
||||
|
||||
// Turn eligible hoisted static trees into stringied static nodes, e.g.
|
||||
// const _hoisted_1 = createStaticVNode(`<div class="foo">bar</div>`)
|
||||
// This is only performed in non-in-browser compilations.
|
||||
export const stringifyStatic: HoistTransform = (node, context) => {
|
||||
if (shouldOptimize(node)) {
|
||||
return createCallExpression(context.helper(CREATE_STATIC), [
|
||||
JSON.stringify(stringifyElement(node, context))
|
||||
])
|
||||
} else {
|
||||
return node.codegenNode!
|
||||
}
|
||||
}
|
||||
|
||||
export const enum StringifyThresholds {
|
||||
ELEMENT_WITH_BINDING_COUNT = 5,
|
||||
NODE_COUNT = 20
|
||||
}
|
||||
|
||||
// Opt-in heuristics based on:
|
||||
// 1. number of elements with attributes > 5.
|
||||
// 2. OR: number of total nodes > 20
|
||||
// For some simple trees, the performance can actually be worse.
|
||||
// it is only worth it when the tree is complex enough
|
||||
// (e.g. big piece of static content)
|
||||
function shouldOptimize(node: ElementNode): boolean {
|
||||
let bindingThreshold = StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
|
||||
let nodeThreshold = StringifyThresholds.NODE_COUNT
|
||||
|
||||
// TODO: check for cases where using innerHTML will result in different
|
||||
// output compared to imperative node insertions.
|
||||
// probably only need to check for most common case
|
||||
// i.e. non-phrasing-content tags inside `<p>`
|
||||
function walk(node: ElementNode) {
|
||||
for (let i = 0; i < node.children.length; i++) {
|
||||
if (--nodeThreshold === 0) {
|
||||
return true
|
||||
}
|
||||
const child = node.children[i]
|
||||
if (child.type === NodeTypes.ELEMENT) {
|
||||
if (child.props.length > 0 && --bindingThreshold === 0) {
|
||||
return true
|
||||
}
|
||||
if (walk(child)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
return walk(node)
|
||||
}
|
||||
|
||||
function stringifyElement(
|
||||
node: ElementNode,
|
||||
context: TransformContext
|
||||
): string {
|
||||
let res = `<${node.tag}`
|
||||
for (let i = 0; i < node.props.length; i++) {
|
||||
const p = node.props[i]
|
||||
if (p.type === NodeTypes.ATTRIBUTE) {
|
||||
res += ` ${p.name}`
|
||||
if (p.value) {
|
||||
res += `="${escapeHtml(p.value.content)}"`
|
||||
}
|
||||
} else if (p.type === NodeTypes.DIRECTIVE && p.name === 'bind') {
|
||||
// constant v-bind, e.g. :foo="1"
|
||||
let evaluated = evaluateConstant(p.exp as SimpleExpressionNode)
|
||||
const arg = p.arg && (p.arg as SimpleExpressionNode).content
|
||||
if (arg === 'class') {
|
||||
evaluated = normalizeClass(evaluated)
|
||||
} else if (arg === 'style') {
|
||||
evaluated = stringifyStyle(normalizeStyle(evaluated))
|
||||
}
|
||||
res += ` ${(p.arg as SimpleExpressionNode).content}="${escapeHtml(
|
||||
evaluated
|
||||
)}"`
|
||||
}
|
||||
}
|
||||
if (context.scopeId) {
|
||||
res += ` ${context.scopeId}`
|
||||
}
|
||||
res += `>`
|
||||
for (let i = 0; i < node.children.length; i++) {
|
||||
res += stringifyNode(node.children[i], context)
|
||||
}
|
||||
if (!isVoidTag(node.tag)) {
|
||||
res += `</${node.tag}>`
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
function stringifyNode(
|
||||
node: string | TemplateChildNode,
|
||||
context: TransformContext
|
||||
): string {
|
||||
if (isString(node)) {
|
||||
return node
|
||||
}
|
||||
if (isSymbol(node)) {
|
||||
return ``
|
||||
}
|
||||
switch (node.type) {
|
||||
case NodeTypes.ELEMENT:
|
||||
return stringifyElement(node, context)
|
||||
case NodeTypes.TEXT:
|
||||
return escapeHtml(node.content)
|
||||
case NodeTypes.COMMENT:
|
||||
return `<!--${escapeHtml(node.content)}-->`
|
||||
case NodeTypes.INTERPOLATION:
|
||||
return escapeHtml(toDisplayString(evaluateConstant(node.content)))
|
||||
case NodeTypes.COMPOUND_EXPRESSION:
|
||||
return escapeHtml(evaluateConstant(node))
|
||||
case NodeTypes.TEXT_CALL:
|
||||
return stringifyNode(node.content, context)
|
||||
default:
|
||||
// static trees will not contain if/for nodes
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
// __UNSAFE__
|
||||
// Reason: eval.
|
||||
// It's technically safe to eval because only constant expressions are possible
|
||||
// here, e.g. `{{ 1 }}` or `{{ 'foo' }}`
|
||||
// in addition, constant exps bail on presence of parens so you can't even
|
||||
// run JSFuck in here. But we mark it unsafe for security review purposes.
|
||||
// (see compiler-core/src/transformExpressions)
|
||||
function evaluateConstant(exp: ExpressionNode): string {
|
||||
if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {
|
||||
return new Function(`return ${exp.content}`)()
|
||||
} else {
|
||||
// compound
|
||||
let res = ``
|
||||
exp.children.forEach(c => {
|
||||
if (isString(c) || isSymbol(c)) {
|
||||
return
|
||||
}
|
||||
if (c.type === NodeTypes.TEXT) {
|
||||
res += c.content
|
||||
} else if (c.type === NodeTypes.INTERPOLATION) {
|
||||
res += toDisplayString(evaluateConstant(c.content))
|
||||
} else {
|
||||
res += evaluateConstant(c)
|
||||
}
|
||||
})
|
||||
return res
|
||||
}
|
||||
}
|
@ -1,7 +1,9 @@
|
||||
import {
|
||||
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)
|
||||
}
|
||||
|
@ -1,5 +0,0 @@
|
||||
import { DirectiveTransform } from 'packages/compiler-core/src/transform'
|
||||
|
||||
export const transformCloak: DirectiveTransform = (node, context) => {
|
||||
return { props: [], needRuntime: false }
|
||||
}
|
@ -24,7 +24,6 @@ export const transformVHtml: DirectiveTransform = (dir, node, context) => {
|
||||
createSimpleExpression(`innerHTML`, true, loc),
|
||||
exp || createSimpleExpression('', true)
|
||||
)
|
||||
],
|
||||
needRuntime: false
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -24,7 +24,6 @@ export const transformVText: DirectiveTransform = (dir, node, context) => {
|
||||
createSimpleExpression(`textContent`, true, loc),
|
||||
exp || createSimpleExpression('', true)
|
||||
)
|
||||
],
|
||||
needRuntime: false
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,43 @@
|
||||
import {
|
||||
NodeTransform,
|
||||
NodeTypes,
|
||||
ElementTypes,
|
||||
ComponentNode,
|
||||
IfBranchNode
|
||||
} from '@vue/compiler-core'
|
||||
import { TRANSITION } from '../runtimeHelpers'
|
||||
import { createDOMCompilerError, DOMErrorCodes } from '../errors'
|
||||
|
||||
export const warnTransitionChildren: NodeTransform = (node, context) => {
|
||||
if (
|
||||
node.type === NodeTypes.ELEMENT &&
|
||||
node.tagType === ElementTypes.COMPONENT
|
||||
) {
|
||||
const component = context.isBuiltInComponent(node.tag)
|
||||
if (component === TRANSITION) {
|
||||
return () => {
|
||||
if (node.children.length && hasMultipleChildren(node)) {
|
||||
context.onError(
|
||||
createDOMCompilerError(
|
||||
DOMErrorCodes.X_TRANSITION_INVALID_CHILDREN,
|
||||
{
|
||||
start: node.children[0].loc.start,
|
||||
end: node.children[node.children.length - 1].loc.end,
|
||||
source: ''
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function hasMultipleChildren(node: ComponentNode | IfBranchNode): boolean {
|
||||
const child = node.children[0]
|
||||
return (
|
||||
node.children.length !== 1 ||
|
||||
child.type === NodeTypes.FOR ||
|
||||
(child.type === NodeTypes.IF && child.branches.some(hasMultipleChildren))
|
||||
)
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
|
||||
exports[`source map 1`] = `
|
||||
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.],
|
||||
]
|
||||
`;
|
||||
|
@ -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 */))
|
||||
}"
|
||||
`;
|
||||
|
@ -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 */))
|
||||
}"
|
||||
|
237
packages/compiler-sfc/__tests__/compileStyle.spec.ts
Normal file
237
packages/compiler-sfc/__tests__/compileStyle.spec.ts
Normal file
@ -0,0 +1,237 @@
|
||||
import { compileStyle } from '../src/compileStyle'
|
||||
import { mockWarn } from '@vue/shared'
|
||||
|
||||
function compile(source: string): string {
|
||||
const res = compileStyle({
|
||||
source,
|
||||
filename: 'test.css',
|
||||
id: 'test'
|
||||
})
|
||||
if (res.errors.length) {
|
||||
res.errors.forEach(err => {
|
||||
console.error(err)
|
||||
})
|
||||
expect(res.errors.length).toBe(0)
|
||||
}
|
||||
return res.code
|
||||
}
|
||||
|
||||
describe('SFC scoped CSS', () => {
|
||||
mockWarn()
|
||||
|
||||
test('simple selectors', () => {
|
||||
expect(compile(`h1 { color: red; }`)).toMatch(`h1[test] { color: red;`)
|
||||
expect(compile(`.foo { color: red; }`)).toMatch(`.foo[test] { color: red;`)
|
||||
})
|
||||
|
||||
test('descendent selector', () => {
|
||||
expect(compile(`h1 .foo { color: red; }`)).toMatch(
|
||||
`h1 .foo[test] { color: red;`
|
||||
)
|
||||
})
|
||||
|
||||
test('multiple selectors', () => {
|
||||
expect(compile(`h1 .foo, .bar, .baz { color: red; }`)).toMatch(
|
||||
`h1 .foo[test], .bar[test], .baz[test] { color: red;`
|
||||
)
|
||||
})
|
||||
|
||||
test('pseudo class', () => {
|
||||
expect(compile(`.foo:after { color: red; }`)).toMatch(
|
||||
`.foo[test]:after { color: red;`
|
||||
)
|
||||
})
|
||||
|
||||
test('pseudo element', () => {
|
||||
expect(compile(`::selection { display: none; }`)).toMatch(
|
||||
'[test]::selection {'
|
||||
)
|
||||
})
|
||||
|
||||
test('spaces before pseudo element', () => {
|
||||
const code = compile(`.abc, ::selection { color: red; }`)
|
||||
expect(code).toMatch('.abc[test],')
|
||||
expect(code).toMatch('[test]::selection {')
|
||||
})
|
||||
|
||||
test('::v-deep', () => {
|
||||
expect(compile(`::v-deep(.foo) { color: red; }`)).toMatchInlineSnapshot(`
|
||||
"[test] .foo { color: red;
|
||||
}"
|
||||
`)
|
||||
expect(compile(`::v-deep(.foo .bar) { color: red; }`))
|
||||
.toMatchInlineSnapshot(`
|
||||
"[test] .foo .bar { color: red;
|
||||
}"
|
||||
`)
|
||||
expect(compile(`.baz .qux ::v-deep(.foo .bar) { color: red; }`))
|
||||
.toMatchInlineSnapshot(`
|
||||
".baz .qux[test] .foo .bar { color: red;
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
test('::v-slotted', () => {
|
||||
expect(compile(`::v-slotted(.foo) { color: red; }`)).toMatchInlineSnapshot(`
|
||||
".foo[test-s] { color: red;
|
||||
}"
|
||||
`)
|
||||
expect(compile(`::v-slotted(.foo .bar) { color: red; }`))
|
||||
.toMatchInlineSnapshot(`
|
||||
".foo .bar[test-s] { color: red;
|
||||
}"
|
||||
`)
|
||||
expect(compile(`.baz .qux ::v-slotted(.foo .bar) { color: red; }`))
|
||||
.toMatchInlineSnapshot(`
|
||||
".baz .qux .foo .bar[test-s] { color: red;
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
test('::v-global', () => {
|
||||
expect(compile(`::v-global(.foo) { color: red; }`)).toMatchInlineSnapshot(`
|
||||
".foo { color: red;
|
||||
}"
|
||||
`)
|
||||
expect(compile(`::v-global(.foo .bar) { color: red; }`))
|
||||
.toMatchInlineSnapshot(`
|
||||
".foo .bar { color: red;
|
||||
}"
|
||||
`)
|
||||
// global ignores anything before it
|
||||
expect(compile(`.baz .qux ::v-global(.foo .bar) { color: red; }`))
|
||||
.toMatchInlineSnapshot(`
|
||||
".foo .bar { color: red;
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
test('media query', () => {
|
||||
expect(compile(`@media print { .foo { color: red }}`))
|
||||
.toMatchInlineSnapshot(`
|
||||
"@media print {
|
||||
.foo[test] { color: red
|
||||
}}"
|
||||
`)
|
||||
})
|
||||
|
||||
test('supports query', () => {
|
||||
expect(compile(`@supports(display: grid) { .foo { display: grid }}`))
|
||||
.toMatchInlineSnapshot(`
|
||||
"@supports(display: grid) {
|
||||
.foo[test] { display: grid
|
||||
}}"
|
||||
`)
|
||||
})
|
||||
|
||||
test('scoped keyframes', () => {
|
||||
const style = compile(`
|
||||
.anim {
|
||||
animation: color 5s infinite, other 5s;
|
||||
}
|
||||
.anim-2 {
|
||||
animation-name: color;
|
||||
animation-duration: 5s;
|
||||
}
|
||||
.anim-3 {
|
||||
animation: 5s color infinite, 5s other;
|
||||
}
|
||||
.anim-multiple {
|
||||
animation: color 5s infinite, opacity 2s;
|
||||
}
|
||||
.anim-multiple-2 {
|
||||
animation-name: color, opacity;
|
||||
animation-duration: 5s, 2s;
|
||||
}
|
||||
|
||||
@keyframes color {
|
||||
from { color: red; }
|
||||
to { color: green; }
|
||||
}
|
||||
@-webkit-keyframes color {
|
||||
from { color: red; }
|
||||
to { color: green; }
|
||||
}
|
||||
@keyframes opacity {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
@-webkit-keyframes opacity {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
`)
|
||||
|
||||
expect(style).toContain(
|
||||
`.anim[test] {\n animation: color-test 5s infinite, other 5s;`
|
||||
)
|
||||
expect(style).toContain(`.anim-2[test] {\n animation-name: color-test`)
|
||||
expect(style).toContain(
|
||||
`.anim-3[test] {\n animation: 5s color-test infinite, 5s other;`
|
||||
)
|
||||
expect(style).toContain(`@keyframes color-test {`)
|
||||
expect(style).toContain(`@-webkit-keyframes color-test {`)
|
||||
|
||||
expect(style).toContain(
|
||||
`.anim-multiple[test] {\n animation: color-test 5s infinite,opacity-test 2s;`
|
||||
)
|
||||
expect(style).toContain(
|
||||
`.anim-multiple-2[test] {\n animation-name: color-test,opacity-test;`
|
||||
)
|
||||
expect(style).toContain(`@keyframes opacity-test {`)
|
||||
expect(style).toContain(`@-webkit-keyframes opacity-test {`)
|
||||
})
|
||||
|
||||
// vue-loader/#1370
|
||||
test('spaces after selector', () => {
|
||||
const { code } = compileStyle({
|
||||
source: `.foo , .bar { color: red; }`,
|
||||
filename: 'test.css',
|
||||
id: 'test'
|
||||
})
|
||||
|
||||
expect(code).toMatchInlineSnapshot(`
|
||||
".foo[test], .bar[test] { color: red;
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
describe('deprecated syntax', () => {
|
||||
test('::v-deep as combinator', () => {
|
||||
expect(compile(`::v-deep .foo { color: red; }`)).toMatchInlineSnapshot(`
|
||||
"[test] .foo { color: red;
|
||||
}"
|
||||
`)
|
||||
expect(compile(`.bar ::v-deep .foo { color: red; }`))
|
||||
.toMatchInlineSnapshot(`
|
||||
".bar[test] .foo { color: red;
|
||||
}"
|
||||
`)
|
||||
expect(
|
||||
`::v-deep usage as a combinator has been deprecated.`
|
||||
).toHaveBeenWarned()
|
||||
})
|
||||
|
||||
test('>>> (deprecated syntax)', () => {
|
||||
const code = compile(`>>> .foo { color: red; }`)
|
||||
expect(code).toMatchInlineSnapshot(`
|
||||
"[test] .foo { color: red;
|
||||
}"
|
||||
`)
|
||||
expect(
|
||||
`the >>> and /deep/ combinators have been deprecated.`
|
||||
).toHaveBeenWarned()
|
||||
})
|
||||
|
||||
test('/deep/ (deprecated syntax)', () => {
|
||||
const code = compile(`/deep/ .foo { color: red; }`)
|
||||
expect(code).toMatchInlineSnapshot(`
|
||||
"[test] .foo { color: red;
|
||||
}"
|
||||
`)
|
||||
expect(
|
||||
`the >>> and /deep/ combinators have been deprecated.`
|
||||
).toHaveBeenWarned()
|
||||
})
|
||||
})
|
||||
})
|
@ -9,7 +9,7 @@ test('should work', () => {
|
||||
expect(result.errors.length).toBe(0)
|
||||
expect(result.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',
|
||||
|
@ -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(
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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",
|
||||
|
@ -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, {
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user