3 |9 | 10 | 11 | 29 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/---feature-suggestion.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F195 Feature suggestion" 3 | about: Suggest an idea 4 | title: 'feat: ' 5 | labels: enhancement 6 | assignees: danielroe 7 | --- 8 | 9 | **🆒 Your use case** 10 | Add a description of your use case, and how this feature would help you. 11 | 12 | > Ex. When I do [...] I would expect to be able to do [...] 13 | 14 | **🆕 The solution you'd like** 15 | Describe what you want to happen. 16 | 17 | **🔍 Alternatives you've considered** 18 | Have you considered any alternative solutions or features? 19 | 20 | **ℹ️ Additional info** 21 | Is there any other context you think would be helpful to know? 22 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-hello-world", 3 | "dependencies": { 4 | "@nuxt/http": "^0.6.4", 5 | "@nuxtjs/composition-api": "latest", 6 | "vue-content-placeholders": "^0.2.1" 7 | }, 8 | "devDependencies": { 9 | "@nuxt/types": "^2.18.1", 10 | "@types/node-fetch": "^2.6.12", 11 | "nuxt": "^2.18.1" 12 | }, 13 | "scripts": { 14 | "dev": "nuxt", 15 | "build": "nuxt build", 16 | "now-build": "NOW_BUILD=true nuxt generate", 17 | "start": "nuxt start", 18 | "generate": "nuxt generate", 19 | "post-update": "yarn upgrade --latest" 20 | }, 21 | "volta": { 22 | "node": "16.16.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test/e2e/promise.ts: -------------------------------------------------------------------------------- 1 | import { Selector } from 'testcafe' 2 | import { navigateTo, expectOnPage } from './helpers' 3 | 4 | // eslint-disable-next-line 5 | fixture`ssrPromise` 6 | 7 | test('Shows data on ssr-loaded page', async t => { 8 | await navigateTo('/promise') 9 | await expectOnPage('promise-server') 10 | 11 | await t.click(Selector('a').withText('home')) 12 | await t.click(Selector('a').withText('promise')) 13 | await expectOnPage('promise-server') 14 | }) 15 | 16 | test('Shows appropriate data on client-loaded page', async t => { 17 | await navigateTo('/') 18 | await t.click(Selector('a').withText('promise')) 19 | await expectOnPage('promise-client') 20 | }) 21 | -------------------------------------------------------------------------------- /example/layouts/default.vue: -------------------------------------------------------------------------------- 1 | 2 |4 |
8 |store: {{ storeExists }}5 |route: {{ routeExists }}6 |router: {{ routerExists }}7 |
3 |7 | 8 | 9 | 36 | -------------------------------------------------------------------------------- /test/e2e/wrappers.ts: -------------------------------------------------------------------------------- 1 | import { Selector } from 'testcafe' 2 | import { navigateTo, expectOnPage, expectPathnameToBe } from './helpers' 3 | 4 | // eslint-disable-next-line 5 | fixture`Wrappers` 6 | 7 | test('Shows data on ssr-loaded page', async () => { 8 | await navigateTo('/wrappers') 9 | await expectOnPage('store: true') 10 | await expectOnPage('route: true') 11 | await expectOnPage('router: true') 12 | }) 13 | 14 | test('Shows appropriate data on client-loaded page', async t => { 15 | await navigateTo('/') 16 | await t.click(Selector('a').withText('wrappers')) 17 | await expectPathnameToBe('/wrappers') 18 | await expectOnPage('store: true') 19 | await expectOnPage('route: true') 20 | await expectOnPage('router: true') 21 | }) 22 | -------------------------------------------------------------------------------- /test/fixture/components/comp.vue: -------------------------------------------------------------------------------- 1 | 2 |4 |
6 |promise-{{ promise }}5 |
reqRefCounter: {{ reqRefCounter }}
4 | ssrRefCounter: {{ ssrRefCounter }}
5 | reqSsrRefCounter: {{ reqSsrRefCounter }}
6 | 3 |10 | 11 | 12 | 40 | -------------------------------------------------------------------------------- /docs/pages/en/6.API/2.useContext.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useContext 3 | description: 'You can access the Nuxt context within the composition API' 4 | --- 5 | 6 | You can access the Nuxt context more easily using `useContext`, which will return the Nuxt context. 7 | 8 | ```ts 9 | import { defineComponent, useContext } from '@nuxtjs/composition-api' 10 | 11 | export default defineComponent({ 12 | setup() { 13 | const { store } = useContext() 14 | store.dispatch('myAction') 15 | }, 16 | }) 17 | ``` 18 | 19 | :::alert{type="info"} 20 | Note that `route`, `query`, `from` and `params` are reactive refs (accessed with `.value`), but the rest of the context is not. 21 | ::: 22 | 23 | :::alert{type="warning"} 24 | To smooth your upgrade to Nuxt 3, it is recommended not to access `route`, `query`, `from` and `params` from `useContext` but rather to use the `useRoute` helper function. 25 | ::: 26 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "rootDir": ".", 5 | "strict": true, 6 | "sourceMap": true, 7 | "allowJs": true, 8 | "checkJs": true, 9 | "target": "ESNext", 10 | "module": "CommonJS", 11 | "moduleResolution": "Node", 12 | "lib": ["ESNext", "DOM"], 13 | "esModuleInterop": true, 14 | "resolveJsonModule": true, 15 | "allowSyntheticDefaultImports": true, 16 | "paths": { 17 | "@nuxtjs/composition-api/dist/runtime/globals": [ 18 | "./src/runtime/globals.ts" 19 | ], 20 | "@nuxtjs/composition-api": ["./src/runtime/index.ts"] 21 | }, 22 | "types": ["node", "@nuxt/types"] 23 | }, 24 | "include": [ 25 | "src/**/*.ts", 26 | "test/fixture/**/*.vue", 27 | "test/fixture/**/*.js", 28 | "test/fixture/**/*.ts", 29 | "tsd/**/*.ts", 30 | "unit/**/*.ts" 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /test/e2e/route.ts: -------------------------------------------------------------------------------- 1 | import { t, Selector } from 'testcafe' 2 | import { navigateTo, expectOnPage } from './helpers' 3 | 4 | // eslint-disable-next-line 5 | fixture`useRoute` 6 | 7 | async function displaysRouteCorrectly() { 8 | await expectOnPage('path: /route/a') 9 | await expectOnPage('watch function called: 1') 10 | await t.click(Selector('a').withText('Link with query')) 11 | await expectOnPage('route query test: true') 12 | await expectOnPage('watch function called: 2') 13 | await expectOnPage('route param slug: a') 14 | } 15 | 16 | test('Shows correct route info on server-loaded page', async () => { 17 | await navigateTo('/route/a') 18 | await displaysRouteCorrectly() 19 | }) 20 | 21 | test('Shows correct route info on client-loaded page', async t => { 22 | await navigateTo('/') 23 | await t.click(Selector('a').withText('route')) 24 | await displaysRouteCorrectly() 25 | }) 26 | -------------------------------------------------------------------------------- /docs/pages/en/3.typings/1.definitionHelpers.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: defineNuxt* 3 | description: 'You can get automatic type-hinting for Nuxt configuration, plugins, middleware, modules and serverMiddleware.' 4 | --- 5 | 6 | There are some helpers to optimize developer experience when creating Nuxt plugins, middleware, server middleware and modules. 7 | 8 | These helpers simply return the function or object passed into them, adding the correct typings. 9 | 10 | ## defineNuxtPlugin 11 | 12 | Create a plugin with types with: 13 | 14 | ```ts 15 | import { defineNuxtPlugin } from '@nuxtjs/composition-api' 16 | 17 | export default defineNuxtPlugin(ctx => { 18 | // do stuff 19 | }) 20 | ``` 21 | 22 | ## defineNuxtMiddleware 23 | 24 | Create middleware with types with: 25 | 26 | ```ts 27 | import { defineNuxtMiddleware } from '@nuxtjs/composition-api' 28 | 29 | export default defineNuxtMiddleware(ctx => { 30 | // do stuff 31 | }) 32 | ``` 33 | -------------------------------------------------------------------------------- /docs/pages/en/6.API/4.wrap.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: wrapProperty 3 | description: 'You can create a custom helper for any Vue instance property.' 4 | --- 5 | 6 | You might want to create a custom helper to 'convert' a non-Composition API property to a Composition-ready one. `wrapProperty` enables you to do that easily, returning either a computed or a bare property as required. 7 | 8 | (The second argument is a boolean indicating whether the helper function should return a computed property or not, and it defaults to `true`.) 9 | 10 | ```ts 11 | import { defineComponent, wrapProperty } from '@nuxtjs/composition-api' 12 | 13 | // For example, for used with https://github.com/danielroe/typed-vuex 14 | const useAccessor = wrapProperty('$accessor', false) 15 | 16 | export default defineComponent({ 17 | setup() { 18 | const accessor = useAccessor() 19 | // You can now access a fully typed store accessor in your component 20 | }, 21 | }) 22 | ``` 23 | -------------------------------------------------------------------------------- /src/module/static.ts: -------------------------------------------------------------------------------- 1 | import { copyFileSync, existsSync, mkdirpSync, readdirSync } from 'fs-extra' 2 | import { join } from 'pathe' 3 | 4 | import type { NuxtOptions } from '@nuxt/types' 5 | import type { ModuleThis } from '@nuxt/types/config/module' 6 | 7 | export function prepareUseStatic(this: ModuleThis) { 8 | const nuxtOptions: NuxtOptions = this.nuxt.options 9 | 10 | const staticPath = join(nuxtOptions.buildDir, 'static-json') 11 | 12 | this.nuxt.hook('builder:prepared', () => { 13 | mkdirpSync(staticPath) 14 | }) 15 | 16 | this.nuxt.hook('generate:route', () => { 17 | mkdirpSync(staticPath) 18 | }) 19 | 20 | this.nuxt.hook('generate:done', async (generator: any) => { 21 | if (!existsSync(staticPath)) return 22 | 23 | const { distPath } = generator 24 | readdirSync(staticPath).forEach(file => 25 | copyFileSync(join(staticPath, file), join(distPath, file)) 26 | ) 27 | }) 28 | 29 | return { staticPath } 30 | } 31 | -------------------------------------------------------------------------------- /docs/pages/en/5.data/1.reqRef.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: reqRef, reqSsrRef 3 | description: 'You can define server-side refs that are reset on each request.' 4 | --- 5 | 6 | `reqRef` declares a normal `ref` with one key difference. It resets the value of this ref on each request. You can find out [more information here](/getting-started/gotchas#shared-server-state). 7 | 8 | :::alert 9 | You do not need a `reqRef` if you are using an `ssrRef` within a component setup function as it will be automatically tied to the per-request state. 10 | ::: 11 | 12 | :::alert{type="warning"} 13 | You should take especial care because of the danger of shared state when using refs in this way. 14 | ::: 15 | 16 | ## Example 17 | 18 | ```ts[~/state/sampleModule.js] 19 | import { reqRef } from '@nuxtjs/composition-api' 20 | 21 | export const user = reqRef(null) 22 | 23 | export const fetchUser = async () => { 24 | const r = await fetch('https://api.com/users') 25 | user.value = await r.json() 26 | } 27 | ``` 28 | -------------------------------------------------------------------------------- /test/fixture/pages/static/_id.vue: -------------------------------------------------------------------------------- 1 | 2 |4 |
6 |ssrRef-{{ noSetup }}5 |7 |
9 |async-{{ async }}8 |
3 |10 | 11 | 12 | 40 | -------------------------------------------------------------------------------- /example/components/Author.vue: -------------------------------------------------------------------------------- 1 | 2 |4 |
6 |static value: {{ JSON.stringify(post) }}5 |7 | Next post 8 | 9 |
3 | Could not fetch Author 4 |
5 |6 | Written by {{ $fetchState.pending ? '...' : user.name }} 7 | 10 |
11 | 12 | 13 | 46 | -------------------------------------------------------------------------------- /example/nuxt.config.js: -------------------------------------------------------------------------------- 1 | import fetch from 'node-fetch' 2 | const serverlessEnvironment = !!process.env.NOW_BUILD 3 | 4 | /** @type {import('@nuxt/types').NuxtConfig} */ 5 | export default { 6 | server: { 7 | port: process.env.PORT || 8000, 8 | }, 9 | target: 'static', 10 | plugins: ['@/plugins/vue-placeholders.js'], 11 | modules: ['@nuxt/http'], 12 | ...(serverlessEnvironment ? { 13 | router: { 14 | base: '/example/', 15 | }, 16 | build: { 17 | publicPath: 'example', 18 | }, 19 | } : {}), 20 | buildModules: ['@nuxtjs/composition-api/module'], 21 | generate: { 22 | interval: 2000, 23 | async routes() { 24 | /** @type {{ userId: number, id: number, title: string, body: string }[]} */ 25 | const posts = await fetch('https://jsonplaceholder.typicode.com/posts') 26 | .then(res => res.json()) 27 | .then(d => d.slice(0, 20)) 28 | const routes = posts.map(post => `/posts/${post.id}`) 29 | 30 | return ['/'].concat(routes) 31 | }, 32 | exclude: [RegExp('/posts/23')] 33 | }, 34 | } 35 | -------------------------------------------------------------------------------- /docs/pages/en/2.packages/2.store.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useStore 3 | description: 'Access this.$store with the Nuxt Composition API.' 4 | --- 5 | 6 | Vuex v4 [provides a helper function](https://next.vuex.vuejs.org/api/#usestore) for accessing it within the Composition API. This helper provides an equivalent while using Nuxt 2 7 | 8 | ## useStore 9 | 10 | Returns `this.$store`. 11 | 12 | ```ts 13 | import { defineComponent, useStore } from '@nuxtjs/composition-api' 14 | 15 | export default defineComponent({ 16 | setup() { 17 | const store = useStore() 18 | }, 19 | }) 20 | ``` 21 | 22 | You can also provide an injection key or custom type to get back a semi-typed store: 23 | 24 | ```ts 25 | import { defineComponent, useStore } from '@nuxtjs/composition-api' 26 | 27 | export interface State { 28 | count: number 29 | } 30 | 31 | export const key: InjectionKey3 |24 | 25 | 26 | 39 | -------------------------------------------------------------------------------- /docs/pages/en/2.packages/1.routes.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useRoute, useRouter 3 | description: 'Access this.$route and this.$router with the Nuxt Composition API.' 4 | --- 5 | 6 | In Vue 3, `vue-router` exports composition functions for accessing the current route and router. 7 | 8 | These helpers provide an equivalent whilst using Nuxt 2. 9 | 10 | ## useRoute 11 | 12 | Returns `this.$route`, wrapped in a computed - so accessible from `.value`. 13 | 14 | ```ts 15 | import { computed, defineComponent, useRoute } from '@nuxtjs/composition-api' 16 | 17 | export default defineComponent({ 18 | setup() { 19 | const route = useRoute() 20 | const id = computed(() => route.value.params.id) 21 | }, 22 | }) 23 | ``` 24 | 25 | :::alert 26 | When migrating to Nuxt 3 you will need to remove `.value` as the native equivalent returns a reactive object, not a computed object. 27 | ::: 28 | 29 | ## useRouter 30 | 31 | Returns `this.$router`. 32 | 33 | ```ts 34 | import { defineComponent, useRouter } from '@nuxtjs/composition-api' 35 | 36 | export default defineComponent({ 37 | setup() { 38 | const router = useRouter() 39 | router.push('/') 40 | }, 41 | }) 42 | ``` 43 | -------------------------------------------------------------------------------- /test/fixture/pages/route/_slug.vue: -------------------------------------------------------------------------------- 1 | 2 |4 |
23 |5 | global setup was {{ ranSsr ? 'run' : 'not run' }} {{ ranSsr }} times on 6 | server 7 |8 |
9 |10 | global setup was {{ ran ? 'run' : 'not run' }} {{ ran }} times on client 11 |12 |
13 |14 | globally injected value was 15 | {{ globalInject ? 'received' : 'not received' }} 16 |17 |
18 |19 | globally injected config from context was 20 | {{ globalConfig.globalInject }} 21 |22 |
3 |21 | 22 | 23 | 50 | -------------------------------------------------------------------------------- /src/module/globals-register.ts: -------------------------------------------------------------------------------- 1 | import type { ModuleThis } from '@nuxt/types/config/module' 2 | import { withTrailingSlash } from 'ufo' 3 | 4 | import { prepareUseStatic } from './static' 5 | import { 6 | addResolvedTemplate, 7 | getNuxtGlobals, 8 | isFullStatic, 9 | isUrl, 10 | } from './utils' 11 | 12 | export function addGlobalsFile(this: ModuleThis) { 13 | const nuxtOptions = this.options 14 | 15 | const { staticPath } = prepareUseStatic.call(this) 16 | const { globalContext, globalNuxt } = getNuxtGlobals.call(this) 17 | 18 | const routerBase = withTrailingSlash(nuxtOptions.router.base) 19 | const publicPath = withTrailingSlash(nuxtOptions.build.publicPath) 20 | 21 | const globals = { 22 | // useFetch 23 | isFullStatic: isFullStatic(nuxtOptions), 24 | // useStatic 25 | staticPath, 26 | publicPath: isUrl(publicPath) ? publicPath : routerBase, 27 | // Throughout 28 | globalContext, 29 | globalNuxt, 30 | } 31 | 32 | const contents = Object.entries(globals) 33 | .map(([key, value]) => `export const ${key} = ${JSON.stringify(value)}`) 34 | .join('\n') 35 | 36 | const globalsFile = addResolvedTemplate.call(this, 'globals.mjs', { 37 | contents, 38 | }) 39 | 40 | nuxtOptions.alias['@nuxtjs/composition-api/dist/runtime/globals'] = 41 | globalsFile 42 | } 43 | -------------------------------------------------------------------------------- /docs/pages/en/2.packages/3.useMeta.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useMeta 3 | description: 'You can define your head and meta properties with the Nuxt Composition API.' 4 | --- 5 | 6 | You can interact directly with [head properties](https://nuxtjs.org/api/pages-head/) in `setup` (and within the `onGlobalSetup` method) by means of the `useMeta()` helper. 7 | 8 | :::alert 9 | In order to enable `useMeta`, please make sure you include `head: {}` within your component definition, and you are using the `defineComponent` exported from `@nuxtjs/composition-api`. 10 | ::: 11 | 12 | ```ts 13 | import { defineComponent, useMeta, computed, ref } from '@nuxtjs/composition-api' 14 | 15 | export default defineComponent({ 16 | // You need to define an empty head to activate this functionality 17 | head: {}, 18 | setup() { 19 | // This will allow you to set the title in head - but won't allow you to read its state outside of this component. 20 | const { title } = useMeta() 21 | title.value = 'My page' 22 | 23 | // You could also provide an initial value. 24 | const { title } = useMeta({ title: 'My page' }) 25 | 26 | // ... or simply set some meta tags 27 | useMeta({ title: 'My page', ... }) 28 | 29 | // You can even pass a function to achieve a computed meta 30 | const message = ref('') 31 | useMeta(() => ({ title: message.value })) 32 | }, 33 | }) 34 | ``` 35 | -------------------------------------------------------------------------------- /test/fixture/pages/meta.vue: -------------------------------------------------------------------------------- 1 | 2 |4 |
6 |path: {{ route.path }}5 |7 |
9 |watch function called: {{ called }}8 |10 |
12 |route query test: {{ query.test }}11 |13 |
15 |route param slug: {{ params.slug }}14 |16 |
20 |- 17 |
19 |Link with query 18 |
3 |9 | 10 | 11 | 57 | -------------------------------------------------------------------------------- /docs/pages/en/6.API/1.useAsync.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useAsync 3 | description: 'You can define async functions that run once and persist the data on client-side.' 4 | --- 5 | 6 | You can create reactive values that depend on asynchronous calls with `useAsync`. 7 | 8 | On the server, this helper will inline the result of the async call in your HTML and automatically inject them into your client code. Much like `asyncData`, it _won't_ re-run these async calls client-side. 9 | 10 | However, if the call hasn't been carried out on SSR (such as if you have navigated to the page after initial load), it returns a `null` ref that is filled with the result of the async call when it resolves. 11 | 12 | The `$http` here has been taken from the [`@nuxt/http` module](https://http.nuxtjs.org/). In order for this example to work, you need to install it (`yarn add @nuxt/http`/`npm install @nuxt/http`) and add it to your `nuxt.config.js` modules list. 13 | 14 | ```ts 15 | import { defineComponent, useAsync, useContext } from '@nuxtjs/composition-api' 16 | 17 | export default defineComponent({ 18 | setup() { 19 | const { $http } = useContext() 20 | const posts = useAsync(() => $http.$get('/api/posts')) 21 | 22 | return { posts } 23 | }, 24 | }) 25 | ``` 26 | 27 | :::alert 28 | At the moment, `useAsync` is only suitable for one-offs, unless you provide your own unique key. [More information](/getting-started/gotchas#keyed-functions). 29 | ::: 30 | -------------------------------------------------------------------------------- /docs/pages/en/1.getting-started/2.setup.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Quick start 3 | description: 'Getting started with the Nuxt Composition API.' 4 | --- 5 | 6 | ## Quick start 7 | 8 | 1. First, install `@nuxtjs/composition-api`: 9 | 10 | :::::code-group 11 | ::::code-block{label="Yarn" active} 12 | 13 | ```bash 14 | yarn add @nuxtjs/composition-api 15 | ``` 16 | 17 | :::: 18 | ::::code-block{label="NPM"} 19 | 20 | ```bash 21 | npm install @nuxtjs/composition-api --save 22 | ``` 23 | 24 | :::: 25 | ::::: 26 | 27 | 2. Enable the module. 28 | 29 | ```js[nuxt.config.js] 30 | { 31 | buildModules: [ 32 | '@nuxtjs/composition-api/module' 33 | ] 34 | } 35 | ``` 36 | 37 | Note that [using `buildModules`](https://nuxtjs.org/api/configuration-modules#-code-buildmodules-code-) requires Nuxt >= 2.9. Just add it to your `modules` if you're on a lower version. 38 | 39 | 3. You're good to go! 40 | 41 | :::alert{type="info"} 42 | 43 | - The module automatically installs [`@vue/composition-api`](https://github.com/vuejs/composition-api) as a plugin, so you do not need to enable it separately. 44 | 45 | - For convenience, this package also exports the [`@vue/composition-api`](https://github.com/vuejs/composition-api) methods and hooks, so you can import directly from `@nuxtjs/composition-api`. 46 | 47 | - For better IDE auto-complete when using `script setup`, follow [these instructions](https://github.com/antfu/unplugin-vue2-script-setup#ide). 48 | 49 | ::: 50 | -------------------------------------------------------------------------------- /example/pages/index.vue: -------------------------------------------------------------------------------- 1 | 2 |4 |
8 |title-{{ title }}5 | 6 | 7 |
Error while fetching posts: {{ $fetchState.error.message }}
11 | 12 | 13 |{{ post.body }}
19 |
20 |
26 |
3 |22 | 23 | 24 | 73 | -------------------------------------------------------------------------------- /src/runtime/composables/hooks.ts: -------------------------------------------------------------------------------- 1 | import type { Plugin } from '@nuxt/types' 2 | import type { SetupContext } from 'vue' 3 | import { getHeadOptions } from './meta' 4 | 5 | import { reqRefs } from './req-ref' 6 | import { setSSRContext } from './ssr-ref' 7 | 8 | type SetupFunction = ( 9 | this: void, 10 | props: Record4 |
6 |ref-{{ computedVal }}5 |7 |
9 |function-{{ funcValue }}8 |10 |
12 |prefetched-{{ prefetchValue }}11 |13 |
15 |on: {{ asyncValue }}14 |16 |
18 |no-change: {{ noChange }}17 |19 |
21 |shallow-{{ shallow.v.deep }}20 |
5 | 6 |
7 |8 |30 |9 |
11 |name-{{ name }}10 |12 |
18 |13 | email-{{ email }} 14 |17 |
15 | loading email 16 |19 |
23 |20 | {{ computedProp }} 21 |22 |24 |
28 |25 | {{ myFunction() }} 26 |27 |29 |