├── lib └── .gitkeep ├── docs ├── .gitignore ├── nuxt.config.js ├── content │ ├── settings.json │ └── en │ │ ├── useFetchExample.md │ │ ├── onGlobalSetup.md │ │ ├── useContext.md │ │ ├── useMeta.md │ │ ├── useAsync.md │ │ ├── index.md │ │ ├── useFetch.md │ │ ├── definitionHelpers.md │ │ ├── shallowSsrRef.md │ │ ├── ssrPromise.md │ │ ├── useStatic.md │ │ ├── setup.md │ │ └── ssrRef.md └── package.json ├── .nowignore ├── test ├── fixture │ ├── api │ │ ├── package.json │ │ └── posts.js │ ├── pages │ │ ├── other.vue │ │ ├── promise.vue │ │ ├── hooks.vue │ │ ├── no-setup.vue │ │ ├── meta.vue │ │ ├── context │ │ │ └── _slug.vue │ │ ├── static │ │ │ └── _id.vue │ │ ├── ssr-ref.vue │ │ └── index.vue │ ├── utils │ │ └── index.js │ ├── plugins │ │ └── global.js │ ├── components │ │ └── comp.vue │ ├── layouts │ │ └── default.vue │ └── nuxt.config.js ├── unit │ ├── setup │ │ └── index.js │ ├── __snapshots__ │ │ ├── babel-ssr-ref.spec.ts.snap │ │ └── ssr-ref.spec.ts.snap │ ├── babel-ssr-ref.spec.ts │ └── ssr-ref.spec.ts ├── tsd │ ├── component.test-d.ts │ ├── index.test-d.ts │ ├── async.test-d.ts │ ├── meta.test-d.ts │ ├── ssr-ref.test-d.ts │ └── createHelpers.test-d.ts └── e2e │ ├── promise.ts │ ├── hooks.ts │ ├── context.ts │ ├── fetch.ts │ ├── meta.ts │ ├── ssr-refs.ts │ ├── helpers │ └── index.ts │ └── static.ts ├── renovate.json ├── .prettierignore ├── .eslintignore ├── prettier.config.js ├── .gitignore ├── .babelrc.js ├── example ├── plugins │ └── vue-placeholders.js ├── README.md ├── package.json ├── layouts │ └── default.vue ├── nuxt.config.js ├── components │ └── Author.vue └── pages │ ├── index.vue │ └── posts │ └── _id.vue ├── jest.config.js ├── src ├── utils.ts ├── plugin.js ├── defineHelpers.ts ├── globals.ts ├── component.ts ├── hooks.ts ├── babel.ts ├── context.ts ├── entrypoint.ts ├── async.ts ├── index.ts ├── meta.ts ├── static.ts ├── ssr-ref.ts └── fetch.ts ├── lint-staged.config.js ├── .release-it.json ├── netlify.toml ├── tsconfig.json ├── .github ├── ISSUE_TEMPLATE │ ├── ---help-wanted.md │ ├── ---documentation.md │ ├── ---feature-suggestion.md │ └── ---bug-report.md └── workflows │ ├── ci.yml │ └── codeql-analysis.yml ├── now.json ├── .eslintrc.js ├── LICENCE ├── rollup.config.js ├── README.md ├── CODE_OF_CONDUCT.md ├── package.json └── CHANGELOG.md /lib/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | static/sw.js -------------------------------------------------------------------------------- /.nowignore: -------------------------------------------------------------------------------- 1 | lib 2 | dist 3 | .github 4 | coverage 5 | 6 | -------------------------------------------------------------------------------- /test/fixture/api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api" 3 | } 4 | -------------------------------------------------------------------------------- /test/fixture/pages/other.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@nuxtjs"], 3 | "ignoreDeps": ["testcafe"] 4 | } 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | lib 2 | example 3 | .nuxt 4 | ./dist 5 | docs 6 | coverage 7 | src/plugin.js -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | lib 2 | test/fixture 3 | test/e2e 4 | example 5 | .nuxt 6 | ./dist 7 | docs 8 | coverage 9 | src/plugin.js 10 | dist -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | semi: false, 3 | singleQuote: true, 4 | trailingComma: 'es5', 5 | arrowParens: 'avoid', 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib/* 2 | !lib/.gitkeep 3 | node_modules 4 | coverage 5 | .nuxt 6 | example/dist 7 | example/yarn.lock 8 | .now 9 | dist 10 | .vercel -------------------------------------------------------------------------------- /.babelrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['@babel/preset-env', '@babel/preset-typescript'], 3 | plugins: ['@babel/plugin-transform-runtime'], 4 | } 5 | -------------------------------------------------------------------------------- /example/plugins/vue-placeholders.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueContentPlaceholders from 'vue-content-placeholders' 3 | 4 | Vue.use(VueContentPlaceholders) 5 | -------------------------------------------------------------------------------- /docs/nuxt.config.js: -------------------------------------------------------------------------------- 1 | import theme from '@nuxt/content-theme-docs' 2 | 3 | export default theme({ 4 | generate: { 5 | routes: ['/'], 6 | exclude: ['/example', '/fixture'] 7 | }, 8 | }) -------------------------------------------------------------------------------- /docs/content/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Nuxt Composition API", 3 | "url": "https://composition-api.nuxtjs.org", 4 | "twitter": "nuxt_js", 5 | "github": "nuxt-community/composition-api" 6 | } -------------------------------------------------------------------------------- /test/fixture/api/posts.js: -------------------------------------------------------------------------------- 1 | module.exports = (req, res) => { 2 | res.setHeader('Content-Type', 'application/json') 3 | return res.end(JSON.stringify({ id: req.url.split('/').slice(-1)[0] })) 4 | } 5 | -------------------------------------------------------------------------------- /test/fixture/utils/index.js: -------------------------------------------------------------------------------- 1 | export function fetcher(result, time = 100) { 2 | return new Promise(resolve => { 3 | return setTimeout(() => { 4 | resolve(result) 5 | }, time) 6 | }) 7 | } 8 | -------------------------------------------------------------------------------- /test/unit/setup/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import CompositionApi from '@vue/composition-api' 3 | 4 | Vue.config.productionTip = false 5 | Vue.config.devtools = false 6 | Vue.use(CompositionApi) 7 | -------------------------------------------------------------------------------- /docs/content/en/useFetchExample.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useFetch 3 | category: Examples 4 | link: https://codesandbox.io/s/github/nuxt-community/composition-api/tree/main/example?from-embed 5 | fullscreen: True 6 | --- 7 | 8 | 9 | -------------------------------------------------------------------------------- /test/tsd/component.test-d.ts: -------------------------------------------------------------------------------- 1 | import { expectType } from 'tsd' 2 | import { defineComponent } from '@vue/composition-api' 3 | 4 | import { defineComponent as modifiedDefineComponent } from '../..' 5 | 6 | expectType(modifiedDefineComponent) 7 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | verbose: true, 3 | testEnvironment: 'node', 4 | coveragePathIgnorePatterns: ['test', '.babelrc.js'], 5 | transform: { 6 | '^.+\\.(js|ts)$': 'babel-jest', 7 | }, 8 | setupFiles: ['/test/unit/setup'], 9 | } 10 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | export function validateKey(key?: T): asserts key is T { 2 | if (!key) { 3 | throw new Error( 4 | "You must provide a key. You can have it generated automatically by adding '@nuxtjs/composition-api/babel' to your Babel plugins." 5 | ) 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "dev": "nuxt", 5 | "generate": "nuxt generate", 6 | "now-build": "yarn generate" 7 | }, 8 | "devDependencies": { 9 | "@nuxt/content-theme-docs": "^0.6.1", 10 | "nuxt": "^2.14.6" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/tsd/index.test-d.ts: -------------------------------------------------------------------------------- 1 | import { expectType } from 'tsd' 2 | import { Context } from '@nuxt/types' 3 | 4 | import { withContext } from '../..' 5 | 6 | // eslint-disable-next-line 7 | expectType(withContext(() => {})) 8 | 9 | withContext(ctx => { 10 | expectType(ctx) 11 | }) 12 | -------------------------------------------------------------------------------- /lint-staged.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | '*.js': ['yarn lint:eslint', 'yarn lint:prettier'], 3 | '*.ts': ['yarn lint:eslint', 'yarn lint:prettier'], 4 | '{!(package)*.json,*.code-snippets,.*rc}': [ 5 | 'yarn lint:prettier --parser json', 6 | ], 7 | 'package.json': ['yarn lint:prettier'], 8 | } 9 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # useFetch() example with Nuxt.js 2 | 3 | This is an example of the `useFetch` composition API hook for use with Nuxt.js >= `v2.12` 4 | 5 | See [live demo](https://composition-api.nuxtjs.org) and [CodeSandbox](https://codesandbox.io/s/github/danielroe/@nuxtjs/composition-api/tree/main/example). 6 | -------------------------------------------------------------------------------- /test/tsd/async.test-d.ts: -------------------------------------------------------------------------------- 1 | import { expectType } from 'tsd' 2 | 3 | import { useAsync, Ref, ssrRef } from '../..' 4 | 5 | expectType>(useAsync(async () => ({ a: 'test' }))) 6 | 7 | expectType>( 8 | useAsync(async () => ({ a: 'test' }), ssrRef(null)) 9 | ) 10 | -------------------------------------------------------------------------------- /test/tsd/meta.test-d.ts: -------------------------------------------------------------------------------- 1 | import { expectType } from 'tsd' 2 | 3 | import { useMeta, Ref } from '../..' 4 | 5 | expectType>(useMeta({ title: 'provided' }).title) 6 | expectType string)>>( 7 | useMeta().titleTemplate 8 | ) 9 | expectType>(useMeta().title) 10 | -------------------------------------------------------------------------------- /.release-it.json: -------------------------------------------------------------------------------- 1 | { 2 | "git": { 3 | "commitMessage": "chore: release v${version}" 4 | }, 5 | "github": { 6 | "release": true, 7 | "releaseName": "v${version}", 8 | "releaseNotes": "echo \"${changelog}\" | sed 1,2d" 9 | }, 10 | "plugins": { 11 | "@release-it/conventional-changelog": { 12 | "preset": "conventionalcommits", 13 | "infile": "CHANGELOG.md" 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/fixture/plugins/global.js: -------------------------------------------------------------------------------- 1 | import { onGlobalSetup, provide, ref, ssrRef } from '@nuxtjs/composition-api' 2 | 3 | export const ranSsr = ssrRef(false) 4 | export const ran = ref(false) 5 | 6 | export default () => { 7 | onGlobalSetup(() => { 8 | ran.value = true 9 | ranSsr.value = true 10 | 11 | provide('globalKey', true) 12 | 13 | return { 14 | ran, 15 | ranSsr, 16 | } 17 | }) 18 | } 19 | -------------------------------------------------------------------------------- /src/plugin.js: -------------------------------------------------------------------------------- 1 | <% if (options.corejsPolyfill === '3') { %> 2 | // Necessary polyfill for Composition API support for IE11 3 | import 'core-js/features/reflect/own-keys' 4 | <% } else if (options.corejsPolyfill === '2') { %> 5 | // Necessary polyfill for Composition API support for IE11 6 | import 'core-js/modules/es6.reflect.own-keys' 7 | <% } %> 8 | 9 | import { globalPlugin } from '@nuxtjs/composition-api' 10 | 11 | export default globalPlugin 12 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | # Global settings applied to the whole site. 2 | # 3 | # “base” is the directory to change to before starting build. If you set base: 4 | # that is where we will look for package.json/.nvmrc/etc, not repo root! 5 | # “command” is your build command. 6 | # “publish” is the directory to publish (relative to the root of your repo). 7 | 8 | [build] 9 | base = "docs" 10 | command = "yarn && yarn generate" 11 | publish = "dist" 12 | -------------------------------------------------------------------------------- /src/defineHelpers.ts: -------------------------------------------------------------------------------- 1 | import { Middleware, Plugin, Module, ServerMiddleware } from '@nuxt/types' 2 | 3 | export const defineNuxtPlugin = (plugin: Plugin) => plugin 4 | export const defineNuxtMiddleware = (middleware: Middleware) => middleware 5 | export const defineNuxtModule = >( 6 | module: Module 7 | ) => module 8 | export const defineNuxtServerMiddleware = ( 9 | serverMiddleware: ServerMiddleware 10 | ) => serverMiddleware 11 | -------------------------------------------------------------------------------- /src/globals.ts: -------------------------------------------------------------------------------- 1 | export const globalNuxt = '<%= options.globalNuxt %>'.includes('options') 2 | ? '$nuxt' 3 | : ('<%= options.globalNuxt %>' as '$nuxt') 4 | 5 | export const globalContext = '<%= options.globalContext %>'.includes('options') 6 | ? '__NUXT__' 7 | : ('<%= options.globalContext %>' as '__NUXT__') 8 | 9 | export const isFullStatic = '<%= options.isFullStatic %>'.includes('options') 10 | ? false 11 | : ('<%= options.isFullStatic %>' as unknown) === 'true' 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "sourceMap": true, 5 | "target": "es2018", 6 | "module": "esnext", 7 | "lib": ["esnext", "dom"], 8 | "esModuleInterop": true, 9 | "moduleResolution": "node", 10 | "rootDir": "src", 11 | "outDir": "lib", 12 | "declaration": false, 13 | "skipLibCheck": true, 14 | "allowSyntheticDefaultImports": true, 15 | "types": ["node", "@nuxt/types", "jest"] 16 | }, 17 | "exclude": ["node_modules", "lib", "test"] 18 | } 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/---help-wanted.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F198 Help" 3 | about: I need help with ... 4 | title: 'help: ' 5 | labels: help 6 | assignees: danielroe 7 | --- 8 | 9 | **📚 What are you trying to do? Please describe.** 10 | A clear and concise description of your objective. Ex. I'm not sure how to [...]. 11 | 12 | **🔍 What have you tried?** 13 | Have you looked through the docs? Tried different approaches? The more detail the better. 14 | 15 | **ℹ️ Additional context** 16 | Add any other context or information. 17 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-hello-world", 3 | "dependencies": { 4 | "@nuxt/http": "^0.5.12", 5 | "@nuxtjs/composition-api": "latest", 6 | "vue-content-placeholders": "^0.2.1" 7 | }, 8 | "devDependencies": { 9 | "nuxt": "latest" 10 | }, 11 | "scripts": { 12 | "dev": "nuxt", 13 | "build": "nuxt build", 14 | "now-build": "NOW_BUILD=true nuxt generate", 15 | "start": "nuxt start", 16 | "generate": "nuxt generate", 17 | "post-update": "yarn upgrade --latest" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/tsd/ssr-ref.test-d.ts: -------------------------------------------------------------------------------- 1 | import { expectType } from 'tsd' 2 | 3 | import { ssrRef, Ref, shallowSsrRef } from '../..' 4 | 5 | expectType>(ssrRef(() => 42)) 6 | expectType>(ssrRef('thoughtless')) 7 | interface Obj { 8 | name: string 9 | } 10 | expectType>(ssrRef({ name: 'today' })) 11 | expectType>(ssrRef(null)) 12 | 13 | expectType>(shallowSsrRef(() => 42)) 14 | expectType>(shallowSsrRef('thoughtless')) 15 | expectType>(shallowSsrRef({ name: 'today' })) 16 | expectType>(shallowSsrRef(null)) 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/---documentation.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F4DA Documentation" 3 | about: How do I ... ? 4 | title: 'docs: ' 5 | labels: documentation 6 | assignees: danielroe 7 | 8 | --- 9 | 10 | **📚 Is your documentation request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I feel I should be able to [...] but I can't see how to do it from the docs. 12 | 13 | **🔍 Where should you find it?** 14 | What page of the docs do you expect this information to be found on? 15 | 16 | **ℹ️ Additional context** 17 | Add any other context or information. 18 | -------------------------------------------------------------------------------- /docs/content/en/onGlobalSetup.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: onGlobalSetup 3 | description: '@nuxtjs/composition-api provides a way to use the Vue 3 Composition API with Nuxt-specific features.' 4 | category: Helpers 5 | fullscreen: True 6 | --- 7 | 8 | This helper will run a callback function in the global setup function. 9 | 10 | ```ts[~/plugins/myPlugin.js] 11 | import { onGlobalSetup, provide } from '@nuxtjs/composition-api' 12 | 13 | export default () => { 14 | onGlobalSetup(() => { 15 | provide('globalKey', true) 16 | }) 17 | } 18 | ``` 19 | 20 | 21 | This should be called from within a plugin rather than in a component context. 22 | 23 | -------------------------------------------------------------------------------- /.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 | 10 | **🆒 Your use case** 11 | Add a description of your use case, and how this feature would help you. 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 | -------------------------------------------------------------------------------- /now.json: -------------------------------------------------------------------------------- 1 | { 2 | "builds": [ 3 | { 4 | "src": "docs/package.json", 5 | "use": "@now/static-build" 6 | }, 7 | { 8 | "src": "test/fixture/api/posts.js", 9 | "use": "@now/node" 10 | }, 11 | { 12 | "src": "package.json", 13 | "use": "@now/static-build" 14 | }, 15 | { 16 | "src": "example/package.json", 17 | "use": "@now/static-build" 18 | } 19 | ], 20 | "routes": [ 21 | { "src": "/api/posts/.*", "dest": "/test/fixture/api/posts.js" }, 22 | { "handle": "filesystem" }, 23 | { "src": "/fixture/(.*)", "dest": "/fixture/200.html" }, 24 | { "src": "/(.*)", "dest": "/docs/$1" } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /src/component.ts: -------------------------------------------------------------------------------- 1 | import { defineComponent as define } from '@vue/composition-api' 2 | 3 | import { getHeadOptions } from './meta' 4 | 5 | /** 6 | * If you want to enable `useMeta`, make sure to include `head: {}` in your component definition. 7 | * @example 8 | ```ts 9 | import { defineComponent } from '@nuxtjs/composition-api' 10 | 11 | export default defineComponent({ 12 | head: {}, 13 | setup() { 14 | ... 15 | } 16 | }) 17 | ``` 18 | */ 19 | export const defineComponent: typeof define = (options: any) => { 20 | if (!('head' in options)) return options 21 | 22 | return { 23 | ...options, 24 | ...getHeadOptions(options), 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /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 | 7 | 8 | 21 | 22 | 35 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/---bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F41B Bug report" 3 | about: Something's not working 4 | title: 'fix: ' 5 | labels: bug 6 | assignees: danielroe 7 | --- 8 | 9 | **🐛 The bug** 10 | What isn't working? Describe what the bug is. 11 | 12 | **🛠️ To reproduce** 13 | Steps to reproduce the behavior: 14 | 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | I'd be very grateful for a link to a gist or repo that reproduces the bug. 21 | 22 | **🌈 Expected behaviour** 23 | What did you expect to happen? Is there a section in the docs about this? 24 | 25 | **ℹ️ Additional context** 26 | Add any other context about the problem here. 27 | -------------------------------------------------------------------------------- /test/unit/__snapshots__/babel-ssr-ref.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`ssrRef babel plugin works 1`] = ` 4 | "const ref = ref(1); 5 | const ref2 = ssrRef(2, \\"ref2-BAW7YFDj4E+Qxr+ujqEADg==\\"); 6 | const ref3 = ssrRef(3, 'custom-key'); 7 | const ref4 = ssrRef(() => 4, \\"ref4-h6IKM1doqCRBR49lWv2V/g==\\"); 8 | const async1 = useAsync(() => null, \\"async1-nk1uJ/q39HMoKEMFH33Ryg==\\"); 9 | const async2 = useAsync(() => null, 'key'); 10 | const stat = useStatic(() => 4, '2', 'post'); 11 | const stat2 = useStatic(() => 4, '2', \\"stat2-55c3e298c9cd3d8ea2665241b6061629\\"); 12 | const stat3 = useStatic(() => 4, \\"stat3-dc9ff012f4c054c3f893c0ea8f3cddc5\\");" 13 | `; 14 | -------------------------------------------------------------------------------- /test/e2e/hooks.ts: -------------------------------------------------------------------------------- 1 | import { Selector } from 'testcafe' 2 | import { navigateTo, expectOnPage } from './helpers' 3 | 4 | // eslint-disable-next-line 5 | fixture`onGlobalSetup` 6 | 7 | const assertions = async () => { 8 | await expectOnPage('global setup was run on server') 9 | await expectOnPage('global setup was run on client') 10 | await expectOnPage('globally injected value was received') 11 | } 12 | 13 | test('Runs plugin on server side page', async () => { 14 | await navigateTo('/hooks') 15 | await assertions() 16 | }) 17 | 18 | test('Runs plugin on client rendered page', async t => { 19 | await navigateTo('/') 20 | await t.click(Selector('a').withText('hooks')) 21 | await assertions() 22 | }) 23 | -------------------------------------------------------------------------------- /test/fixture/components/comp.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 37 | -------------------------------------------------------------------------------- /test/fixture/layouts/default.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 32 | -------------------------------------------------------------------------------- /test/fixture/pages/promise.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 36 | -------------------------------------------------------------------------------- /docs/content/en/useContext.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useContext 3 | description: '@nuxtjs/composition-api provides a way to use the Vue 3 Composition API with Nuxt-specific features.' 4 | category: Helpers 5 | fullscreen: True 6 | --- 7 | 8 | You can access the Nuxt context more easily using `useContext`, which will return the Nuxt context. 9 | 10 | ```ts 11 | import { defineComponent, ref, useContext } from '@nuxtjs/composition-api' 12 | 13 | export default defineComponent({ 14 | setup() { 15 | const { store } = useContext() 16 | store.dispatch('myAction') 17 | }, 18 | }) 19 | ``` 20 | 21 | 22 | 23 | Note that `route`, `query`, `from` and `params` are reactive refs (accessed with `.value`), but the rest of the context is not. 24 | 25 | 26 | -------------------------------------------------------------------------------- /test/fixture/pages/hooks.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 27 | -------------------------------------------------------------------------------- /test/unit/babel-ssr-ref.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | const babel = require('@babel/core') 3 | const plugin = require('../../lib/babel') 4 | /* eslint-enable */ 5 | 6 | const example = ` 7 | const ref = ref(1) 8 | const ref2 = ssrRef(2) 9 | const ref3 = ssrRef(3, 'custom-key') 10 | const ref4 = ssrRef(() => 4) 11 | 12 | const async1 = useAsync(() => null) 13 | const async2 = useAsync(() => null, 'key') 14 | 15 | const stat = useStatic(() => 4, '2', 'post') 16 | const stat2 = useStatic(() => 4, '2') 17 | const stat3 = useStatic(() => 4) 18 | ` 19 | 20 | describe('ssrRef babel plugin', () => { 21 | it('works', () => { 22 | const { code } = babel.transform(example, { plugins: [plugin] })! 23 | expect(code).toMatchSnapshot() 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | browser: true, 5 | es6: true, 6 | node: true, 7 | 'jest/globals': true, 8 | }, 9 | plugins: ['jest'], 10 | rules: { 11 | 'prettier/prettier': [ 12 | 1, 13 | { 14 | semi: false, 15 | singleQuote: true, 16 | trailingComma: 'es5', 17 | arrowParens: 'avoid', 18 | }, 19 | ], 20 | '@typescript-eslint/no-inferrable-types': 1, 21 | '@typescript-eslint/explicit-module-boundary-types': 0, 22 | '@typescript-eslint/explicit-function-return-type': 0, 23 | }, 24 | extends: [ 25 | 'plugin:promise/recommended', 26 | 'plugin:@typescript-eslint/recommended', 27 | 'plugin:prettier/recommended', 28 | 'prettier', 29 | 'prettier/@typescript-eslint', 30 | 'plugin:jest/recommended', 31 | ], 32 | } 33 | -------------------------------------------------------------------------------- /test/fixture/pages/no-setup.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 40 | -------------------------------------------------------------------------------- /test/tsd/createHelpers.test-d.ts: -------------------------------------------------------------------------------- 1 | import { 2 | defineNuxtPlugin, 3 | defineNuxtMiddleware, 4 | defineNuxtModule, 5 | defineNuxtServerMiddleware, 6 | } from '../..' 7 | import { expectType } from 'tsd' 8 | 9 | defineNuxtPlugin((context, inject) => { 10 | const hello = (msg: string) => console.log(`Hello ${msg}!`) 11 | 12 | expectType(context.isClient) 13 | 14 | inject('hello', hello) 15 | }) 16 | 17 | defineNuxtModule<{ 18 | option: string 19 | }>(function (options) { 20 | // expectType(this.options.rootDir) 21 | 22 | this.addPlugin('filename') 23 | 24 | expectType(options.option) 25 | }) 26 | 27 | defineNuxtMiddleware(({ store, redirect }) => { 28 | if (!store.state.authenticated) { 29 | return redirect('/login') 30 | } 31 | }) 32 | 33 | defineNuxtServerMiddleware((req, res, next) => { 34 | expectType(req.url) 35 | 36 | next() 37 | }) 38 | -------------------------------------------------------------------------------- /test/e2e/context.ts: -------------------------------------------------------------------------------- 1 | import { t, Selector } from 'testcafe' 2 | import { navigateTo, expectOnPage } from './helpers' 3 | 4 | // eslint-disable-next-line 5 | fixture`useContext` 6 | 7 | async function displaysContextCorrectly() { 8 | await expectOnPage('path: /context/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 context info on server-loaded page', async () => { 17 | await navigateTo('/context/a') 18 | await displaysContextCorrectly() 19 | }) 20 | 21 | test('Shows correct context info on client-loaded page', async t => { 22 | await navigateTo('/') 23 | await t.click(Selector('a').withText('context')) 24 | await displaysContextCorrectly() 25 | }) 26 | -------------------------------------------------------------------------------- /example/nuxt.config.js: -------------------------------------------------------------------------------- 1 | const fetch = require('node-fetch') 2 | const serverlessEnvironment = !!process.env.NOW_BUILD 3 | 4 | export default { 5 | server: { 6 | port: process.env.PORT || 8000, 7 | }, 8 | target: 'static', 9 | plugins: ['@/plugins/vue-placeholders.js'], 10 | modules: ['@nuxt/http'], 11 | ...(serverlessEnvironment ? { 12 | router: { 13 | base: '/example/', 14 | }, 15 | build: { 16 | publicPath: 'example', 17 | }, 18 | } : {}), 19 | buildModules: ['@nuxtjs/composition-api'], 20 | generate: { 21 | interval: 2000, 22 | async routes() { 23 | const posts = await fetch('https://jsonplaceholder.typicode.com/posts') 24 | .then(res => res.json()) 25 | .then(d => d.slice(0, 20)) 26 | const routes = posts.map(post => `/posts/${post.id}`) 27 | 28 | return ['/'].concat(routes) 29 | }, 30 | exclude: ['/posts/23'] 31 | }, 32 | } 33 | -------------------------------------------------------------------------------- /example/components/Author.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 44 | -------------------------------------------------------------------------------- /docs/content/en/useMeta.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useMeta 3 | description: '@nuxtjs/composition-api provides a way to use the Vue 3 Composition API with Nuxt-specific features.' 4 | category: Helpers 5 | fullscreen: True 6 | --- 7 | 8 | You can interact directly with [head properties](https://nuxtjs.org/api/pages-head/) in `setup` by means of the `useMeta()` helper. 9 | 10 | ```ts 11 | import { defineComponent, useMeta, computed } from '@nuxtjs/composition-api' 12 | 13 | export default defineComponent({ 14 | // You need to define an empty head to activate this functionality 15 | head: {}, 16 | setup() { 17 | // This will allow you to set the title in head - but won't allow you to read its state outside of this component. 18 | const { title } = useMeta() 19 | title.value = 'My page' 20 | 21 | // You could also provide an initial value. 22 | const { title } = useMeta({ title: 'My page' }) 23 | 24 | // ... or simply set some meta tags 25 | useMeta({ title: 'My page', ... }) 26 | }, 27 | }) 28 | ``` 29 | -------------------------------------------------------------------------------- /docs/content/en/useAsync.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useAsync 3 | description: '@nuxtjs/composition-api provides a way to use the Vue 3 Composition API with Nuxt-specific features.' 4 | category: Helpers 5 | fullscreen: True 6 | --- 7 | 8 | You can create reactive values that depend on asynchronous calls with `useAsync`. 9 | 10 | 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. 11 | 12 | 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. 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 | -------------------------------------------------------------------------------- /test/fixture/pages/meta.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 44 | -------------------------------------------------------------------------------- /test/unit/__snapshots__/ssr-ref.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`ssrRef reactivity ssrRefs react to change in state 1`] = ` 4 | Object { 5 | "nuxt": Object { 6 | "ssrRefs": Object { 7 | "name": "full name", 8 | }, 9 | }, 10 | } 11 | `; 12 | 13 | exports[`ssrRef reactivity ssrRefs react to deep change in array state 1`] = ` 14 | Object { 15 | "nuxt": Object { 16 | "ssrRefs": Object { 17 | "obj": Object { 18 | "deep": Object { 19 | "object": Array [ 20 | Object { 21 | "name": "full name", 22 | }, 23 | ], 24 | }, 25 | }, 26 | }, 27 | }, 28 | } 29 | `; 30 | 31 | exports[`ssrRef reactivity ssrRefs react to deep change in object state 1`] = ` 32 | Object { 33 | "nuxt": Object { 34 | "ssrRefs": Object { 35 | "obj": Object { 36 | "deep": Object { 37 | "object": Object { 38 | "name": "full name", 39 | }, 40 | }, 41 | }, 42 | }, 43 | }, 44 | } 45 | `; 46 | -------------------------------------------------------------------------------- /test/fixture/pages/context/_slug.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 47 | -------------------------------------------------------------------------------- /docs/content/en/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Introduction 3 | description: '@nuxtjs/composition-api provides a way to use the Vue 3 Composition API with Nuxt-specific features.' 4 | category: Getting started 5 | position: 1 6 | items: 7 | - Support for the new Nuxt fetch in v2.12+ 8 | - Easy access to router, app, store within setup() 9 | - Interact directly with your vue-meta properties within setup() 10 | - Drop-in replacement for ref with automatic SSR stringification and hydration (ssrRef) 11 | - Written in TypeScript 12 | 13 | --- 14 | 15 | > `@nuxtjs/composition-api` provides a way to use the Vue 3 Composition API in with Nuxt-specific features. 16 | 17 | **Note**: the main aim is to allow experimentation and feedback before the final release of Nuxt 3. It is not recommended to use this package in production. 18 | 19 | ## Key features 20 | 21 | 22 | 23 | ## API reference 24 | 25 | This package extends `@vue/composition-api` and exports all of its types and methods. You can find [a full reference for it here](https://composition-api.vuejs.org/api.html). 26 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Daniel Roe 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /test/fixture/pages/static/_id.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 46 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | lint: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v1 17 | 18 | - uses: actions/cache@v1 19 | id: cache 20 | with: 21 | path: "node_modules" 22 | key: ${{ runner.os }}-${{ hashFiles('yarn.lock') }} 23 | 24 | - name: Install dependencies 25 | if: steps.cache.outputs.cache-hit != 'true' 26 | run: yarn 27 | 28 | - name: Lint project 29 | run: yarn lint 30 | 31 | test: 32 | strategy: 33 | matrix: 34 | os: ["ubuntu-latest", "windows-latest"] 35 | runs-on: "${{ matrix.os }}" 36 | 37 | steps: 38 | - uses: actions/checkout@v1 39 | 40 | - uses: actions/cache@v1 41 | id: cache 42 | with: 43 | path: "node_modules" 44 | key: ${{ runner.os }}-${{ hashFiles('yarn.lock') }} 45 | 46 | - name: Install dependencies 47 | if: steps.cache.outputs.cache-hit != 'true' 48 | run: yarn 49 | 50 | - name: Build project 51 | run: yarn build 52 | 53 | - name: Test project 54 | run: yarn test 55 | -------------------------------------------------------------------------------- /example/pages/index.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 53 | -------------------------------------------------------------------------------- /test/e2e/fetch.ts: -------------------------------------------------------------------------------- 1 | import { Selector } from 'testcafe' 2 | import { 3 | navigateTo, 4 | expectOnPage, 5 | expectPathnameToBe, 6 | expectNotOnPage, 7 | } from './helpers' 8 | 9 | // eslint-disable-next-line 10 | fixture`useFetch` 11 | 12 | test('Shows fetched data on ssr-loaded page', async t => { 13 | await navigateTo('/') 14 | await expectOnPage('name-Full Name') 15 | await expectOnPage('component-Component data') 16 | }) 17 | 18 | test('Shows fetched data on client-loaded page', async t => { 19 | await navigateTo('/other') 20 | await t.click(Selector('a')) 21 | await expectPathnameToBe('/') 22 | await expectOnPage('name-Full Name') 23 | await expectOnPage('component-Component data') 24 | }) 25 | 26 | test('Shows loading state', async t => { 27 | await navigateTo('/other') 28 | await t.click(Selector('a')) 29 | await expectPathnameToBe('/') 30 | await expectOnPage('loading email') 31 | await t.wait(4000) 32 | await expectOnPage('long@load.com') 33 | await expectNotOnPage('loading email') 34 | }) 35 | 36 | test('Refetches with $fetch', async t => { 37 | await navigateTo('/') 38 | await expectNotOnPage('loading email') 39 | await t.click(Selector('button')) 40 | await expectOnPage('loading email') 41 | }) 42 | 43 | test("Doesn't overwrite methods and getters", async () => { 44 | await navigateTo('/') 45 | await expectOnPage('computed') 46 | await expectOnPage('function result') 47 | }) 48 | -------------------------------------------------------------------------------- /test/e2e/meta.ts: -------------------------------------------------------------------------------- 1 | import { Selector } from 'testcafe' 2 | import { navigateTo, expectOnPage } from './helpers' 3 | 4 | // eslint-disable-next-line 5 | fixture`useMeta` 6 | 7 | test('Shows correct title on server-loaded page', async t => { 8 | await navigateTo('/meta') 9 | await t.expect(Selector('title').innerText).eql('newSetTitle - fixture') 10 | await expectOnPage('title-newSetTitle') 11 | await t.expect(Selector('title').innerText).eql('mounted title - fixture') 12 | await expectOnPage('title-mounted title') 13 | await t.expect(Selector('body').getAttribute('class')).eql('dark-mode mobile') 14 | 15 | await t.click(Selector('button').withText('Change')) 16 | await t.expect(Selector('title').innerText).eql('mounted title - Changed') 17 | 18 | await t.click(Selector('a').withText('back')) 19 | await t.expect(Selector('title').innerText).eql('My fixture - fixture') 20 | }) 21 | 22 | test('Shows correct title on client-loaded page', async t => { 23 | await navigateTo('/') 24 | await t.expect(Selector('title').innerText).eql('My fixture - fixture') 25 | 26 | await t.click(Selector('a').withText('meta')) 27 | await t.expect(Selector('title').innerText).eql('newSetTitle - fixture') 28 | 29 | await t.expect(Selector('title').innerText).eql('mounted title - fixture') 30 | 31 | await t.click(Selector('button').withText('Change')) 32 | await t.expect(Selector('title').innerText).eql('mounted title - Changed') 33 | }) 34 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import typescript from 'rollup-plugin-typescript2' 2 | import dts from 'rollup-plugin-dts' 3 | import copy from 'rollup-plugin-copy' 4 | 5 | import pkg from './package.json' 6 | 7 | export default [ 8 | ...['src/index.ts', 'src/entrypoint.ts'].map(input => ({ 9 | input, 10 | output: [ 11 | { 12 | dir: 'lib', 13 | format: 'es', 14 | }, 15 | { 16 | dir: 'lib/cjs', 17 | format: 'cjs', 18 | }, 19 | ], 20 | external: [ 21 | ...Object.keys(pkg.dependencies || {}), 22 | ...Object.keys(pkg.peerDependencies || {}), 23 | ], 24 | plugins: [ 25 | typescript({ 26 | typescript: require('typescript'), 27 | }), 28 | copy({ 29 | targets: [{ src: 'src/plugin.js', dest: 'lib' }], 30 | }), 31 | ], 32 | })), 33 | { 34 | input: 'src/entrypoint.ts', 35 | output: [{ file: 'lib/index.d.ts', format: 'es' }], 36 | plugins: [dts()], 37 | }, 38 | { 39 | input: 'src/babel.ts', 40 | output: [ 41 | { 42 | file: 'lib/babel.js', 43 | format: 'cjs', 44 | }, 45 | { 46 | file: 'lib/babel.es.js', 47 | format: 'es', 48 | }, 49 | ], 50 | plugins: [ 51 | typescript({ 52 | typescript: require('typescript'), 53 | tsconfigOverride: { 54 | compilerOptions: { declaration: false }, 55 | }, 56 | }), 57 | ], 58 | }, 59 | ] 60 | -------------------------------------------------------------------------------- /docs/content/en/useFetch.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useFetch 3 | description: '@nuxtjs/composition-api provides a way to use the Vue 3 Composition API with Nuxt-specific features.' 4 | category: Helpers 5 | fullscreen: True 6 | --- 7 | 8 | Versions of Nuxt newer than v2.12 support a [custom hook called `fetch`](https://nuxtjs.org/api/pages-fetch/) that allows server-side and client-side asynchronous data-fetching. 9 | 10 | You can access this with this package as follows: 11 | 12 | ```ts 13 | import { defineComponent, ref, useFetch } from '@nuxtjs/composition-api' 14 | import axios from 'axios' 15 | 16 | export default defineComponent({ 17 | setup() { 18 | const name = ref('') 19 | 20 | const { fetch, fetchState } = useFetch(async () => { 21 | name.value = await axios.get('https://myapi.com/name') 22 | }) 23 | 24 | // Manually trigger a refetch 25 | fetch() 26 | 27 | // Access fetch error, pending and timestamp 28 | fetchState 29 | 30 | return { name } 31 | }, 32 | }) 33 | ``` 34 | 35 | 36 | 37 | `useFetch` must be called synchronously within `setup()`. Any changes made to component data - that is, to properties _returned_ from `setup()` - will be sent to the client and directly loaded. Other side-effects of `useFetch` hook will not be persisted. 38 | 39 | 40 | 41 | 42 | 43 | `$fetch` and `$fetchState` will already be defined on the instance - so no need to return `fetch` or `fetchState` from setup. 44 | 45 | -------------------------------------------------------------------------------- /src/hooks.ts: -------------------------------------------------------------------------------- 1 | import type { Plugin } from '@nuxt/types' 2 | import type { SetupContext } from '@vue/composition-api' 3 | 4 | import { setSSRContext } from './ssr-ref' 5 | 6 | type SetupFunction = ( 7 | this: void, 8 | props: Record, 9 | ctx: SetupContext 10 | ) => void | Record 11 | 12 | const globalSetup = new Set() 13 | 14 | /** 15 | * Run a callback function in the global setup function. This should be called from a Nuxt plugin. 16 | * @param fn The function to run in the setup function. It receives the global props and context. 17 | * @example 18 | ```ts 19 | import { onGlobalSetup } from '@nuxtjs/composition-api' 20 | 21 | export default () => { 22 | onGlobalSetup(() => { 23 | provide('globalKey', true) 24 | }) 25 | } 26 | ``` 27 | */ 28 | export const onGlobalSetup = (fn: SetupFunction) => { 29 | globalSetup.add(fn) 30 | } 31 | 32 | /** 33 | * @private 34 | */ 35 | export const globalPlugin: Plugin = context => { 36 | const { setup } = context.app 37 | context.app.setup = (...args) => { 38 | let result = {} 39 | if (setup instanceof Function) { 40 | result = setup(...args) || {} 41 | } 42 | for (const fn of globalSetup) { 43 | result = { ...result, ...(fn(...args) || {}) } 44 | } 45 | return result 46 | } 47 | 48 | if (!process.server) return 49 | if (context.app.context.ssrContext) { 50 | setSSRContext(context.app.context.ssrContext) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /docs/content/en/definitionHelpers.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Definition helpers 3 | description: '@nuxtjs/composition-api provides a way to use the Vue 3 Composition API with Nuxt-specific features.' 4 | category: Helpers 5 | position: 3 6 | --- 7 | 8 | There are some helpers to optimize developer experience when creating Nuxt plugins, middleware, server middleware and modules. 9 | 10 | These helpers simply return the function passed into them, adding the correct typings. 11 | 12 | ## defineNuxtPlugin 13 | 14 | Create a plugin with types with: 15 | 16 | ```ts 17 | import { defineNuxtPlugin } from '@nuxtjs/composition-api' 18 | 19 | export default defineNuxtPlugin((ctx) => { 20 | // do stuff 21 | }) 22 | ``` 23 | 24 | ## defineNuxtMiddleware 25 | 26 | Create middleware with types with: 27 | 28 | ```ts 29 | import { defineNuxtMiddleware } from '@nuxtjs/composition-api' 30 | 31 | export default defineNuxtMiddleware((ctx) => { 32 | // do stuff 33 | }) 34 | ``` 35 | 36 | ## defineNuxtModule 37 | 38 | Create a Nuxt module with types with: 39 | 40 | ```ts 41 | import { defineNuxtModule } from '@nuxtjs/composition-api' 42 | 43 | export default defineNuxtModule<{ myOption: boolean }>((moduleOptions) => { 44 | // do stuff 45 | }) 46 | ``` 47 | 48 | 49 | ## defineNuxtServerMiddleware 50 | 51 | Create server middleware with types with: 52 | 53 | ```ts 54 | import { defineNuxtServerMiddleware } from '@nuxtjs/composition-api' 55 | 56 | export default defineNuxtServerMiddleware((req, res, next) => { 57 | // do stuff 58 | }) 59 | ``` 60 | 61 | -------------------------------------------------------------------------------- /example/pages/posts/_id.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 62 | -------------------------------------------------------------------------------- /docs/content/en/shallowSsrRef.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: shallowSsrRef 3 | description: '@nuxtjs/composition-api provides a way to use the Vue 3 Composition API with Nuxt-specific features.' 4 | category: Helpers 5 | fullscreen: True 6 | --- 7 | 8 | This helper creates a [`shallowRef`](https://vue-composition-api-rfc.netlify.app/api.html#shallowref) (a ref that tracks its own .value mutation but doesn't make its value reactive) that is synced between client & server. 9 | 10 | ```ts 11 | import { shallowSsrRef, onMounted } from '@nuxtjs/composition-api' 12 | 13 | const shallow = shallowSsrRef({ v: 'init' }) 14 | if (process.server) shallow.value = { v: 'changed' } 15 | 16 | // On client-side, shallow.value will be { v: changed } 17 | onMounted(() => { 18 | // This and other changes outside of setup won't trigger component updates. 19 | shallow.value.v = 'Hello World' 20 | }) 21 | ``` 22 | 23 | 24 | 25 | At the moment, an `shallowSsrRef` is only suitable for one-offs, unless you provide your own unique key. 26 | 27 | This is because server and client `shallowSsrRef` matches up based on line number within your code. 28 | 29 | ```ts 30 | function useMyFeature() { 31 | // Only one unique key is generated 32 | const feature = shallowSsrRef('') 33 | return feature 34 | } 35 | 36 | const a = useMyFeature() 37 | const b = useMyFeature() 38 | 39 | b.value = 'changed' 40 | // On client-side, a's value will also be initialised to 'changed' 41 | ``` 42 | 43 | If you want to use this pattern, make sure to set a unique key based on each calling of the function. 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/babel.ts: -------------------------------------------------------------------------------- 1 | import * as types from '@babel/types' 2 | import crypto from 'crypto' 3 | 4 | import type { Visitor } from '@babel/traverse' 5 | 6 | interface Babel { 7 | types: typeof types 8 | loadOptions: () => Record 9 | getEnv: () => string 10 | } 11 | 12 | export default function ssrRefPlugin({ loadOptions, getEnv, types: t }: Babel) { 13 | const env = getEnv() 14 | const cwd = env === 'test' ? '' : loadOptions().cwd 15 | 16 | let varName = '' 17 | const visitor: Visitor = { 18 | ...(env !== 'production' 19 | ? { 20 | VariableDeclarator(path) { 21 | varName = 'name' in path.node.id ? `${path.node.id.name}-` : '' 22 | }, 23 | } 24 | : {}), 25 | CallExpression(path) { 26 | if (!('name' in path.node.callee)) return 27 | 28 | let method: crypto.HexBase64Latin1Encoding = 'base64' 29 | 30 | switch (path.node.callee.name) { 31 | case 'useStatic': 32 | if (path.node.arguments.length > 2) return 33 | if (path.node.arguments.length === 2) path.node.arguments.push() 34 | method = 'hex' 35 | break 36 | 37 | case 'shallowSsrRef': 38 | case 'ssrPromise': 39 | case 'ssrRef': 40 | case 'useAsync': 41 | if (path.node.arguments.length > 1) return 42 | break 43 | 44 | default: 45 | return 46 | } 47 | 48 | const hash = crypto.createHash('md5') 49 | hash.update(`${cwd}-${path.node.callee.start}`) 50 | const digest = hash.digest(method).toString() 51 | path.node.arguments.push(t.stringLiteral(`${varName}${digest}`)) 52 | }, 53 | } 54 | return { visitor } 55 | } 56 | -------------------------------------------------------------------------------- /src/context.ts: -------------------------------------------------------------------------------- 1 | import { getCurrentInstance, computed } from '@vue/composition-api' 2 | 3 | import type { Ref } from '@vue/composition-api' 4 | import type { Context } from '@nuxt/types' 5 | import type { Route } from 'vue-router' 6 | 7 | import { globalNuxt } from './globals' 8 | 9 | interface ContextCallback { 10 | (context: Context): void 11 | } 12 | 13 | /** 14 | * @deprecated 15 | * Recommend using `useContext` instead 16 | */ 17 | export const withContext = (callback: ContextCallback) => { 18 | const vm = getCurrentInstance() 19 | if (!vm) throw new Error('This must be called within a setup function.') 20 | 21 | callback(vm[globalNuxt].context) 22 | } 23 | 24 | interface UseContextReturn 25 | extends Omit { 26 | route: Ref 27 | query: Ref 28 | from: Ref 29 | params: Ref 30 | } 31 | 32 | /** 33 | * `useContext` will return the Nuxt context. 34 | * @example 35 | ```ts 36 | import { defineComponent, ref, useContext } from '@nuxtjs/composition-api' 37 | 38 | export default defineComponent({ 39 | setup() { 40 | const { store } = useContext() 41 | store.dispatch('myAction') 42 | }, 43 | }) 44 | ``` 45 | */ 46 | export const useContext = (): UseContextReturn => { 47 | const vm = getCurrentInstance() 48 | if (!vm) throw new Error('This must be called within a setup function.') 49 | 50 | return { 51 | ...vm[globalNuxt].context, 52 | route: computed(() => vm.$route), 53 | query: computed(() => vm.$route.query), 54 | from: computed(() => vm.$route.redirectedFrom), 55 | params: computed(() => vm.$route.params), 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/entrypoint.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import CompositionApi from '@vue/composition-api' 3 | 4 | Vue.use(CompositionApi) 5 | 6 | export { useAsync } from './async' 7 | export { defineComponent } from './component' 8 | export { useContext, withContext } from './context' 9 | export { useFetch } from './fetch' 10 | export { globalPlugin, onGlobalSetup } from './hooks' 11 | export { useMeta } from './meta' 12 | export { ssrRef, shallowSsrRef, setSSRContext, ssrPromise } from './ssr-ref' 13 | export { useStatic } from './static' 14 | export * from './defineHelpers' 15 | 16 | export type { 17 | ComponentRenderProxy, 18 | ComputedRef, 19 | FlushMode, 20 | InjectionKey, 21 | PropOptions, 22 | PropType, 23 | Ref, 24 | SetupContext, 25 | ShallowUnwrapRef, 26 | UnwrapRef, 27 | VueWatcher, 28 | WatchCallback, 29 | WatchEffect, 30 | WatchOptions, 31 | WatchOptionsBase, 32 | WatchSource, 33 | WatchStopHandle, 34 | WritableComputedRef, 35 | } from '@vue/composition-api' 36 | 37 | export { 38 | computed, 39 | createApp, 40 | customRef, 41 | getCurrentInstance, 42 | h, 43 | inject, 44 | isRaw, 45 | isReactive, 46 | isReadonly, 47 | isRef, 48 | markRaw, 49 | nextTick, 50 | onActivated, 51 | onBeforeMount, 52 | onBeforeUnmount, 53 | onBeforeUpdate, 54 | onDeactivated, 55 | onErrorCaptured, 56 | onMounted, 57 | onServerPrefetch, 58 | onUnmounted, 59 | onUpdated, 60 | provide, 61 | proxyRefs, 62 | reactive, 63 | ref, 64 | set, 65 | shallowReactive, 66 | shallowReadonly, 67 | shallowRef, 68 | toRaw, 69 | toRef, 70 | toRefs, 71 | triggerRef, 72 | unref, 73 | useCSSModule, 74 | version, 75 | watch, 76 | watchEffect, 77 | } from '@vue/composition-api' 78 | -------------------------------------------------------------------------------- /test/fixture/nuxt.config.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line 2 | const { resolve } = require('path') 3 | 4 | const routes = ['/context/a', '/static/1', '/static/2', '/static/3'] 5 | const interval = 3000 6 | 7 | const isGenerated = [process.env.GENERATE, process.env.NOW_BUILD].includes( 8 | 'true' 9 | ) 10 | const isPublic = process.env.NOW_BUILD === 'true' 11 | const isTesting = process.env.NODE_ENV !== 'development' && !isPublic 12 | 13 | const rootDir = resolve(__dirname, '../..') 14 | 15 | /** 16 | * @type {import('@nuxt/types').Configuration} 17 | */ 18 | module.exports = { 19 | target: isGenerated ? 'static' : 'server', 20 | rootDir, 21 | buildDir: resolve(__dirname, '.nuxt'), 22 | srcDir: __dirname, 23 | plugins: [resolve(__dirname, './plugins/global.js')], 24 | serverMiddleware: [ 25 | { 26 | path: '/api/posts', 27 | handler: './api/posts', 28 | }, 29 | ], 30 | head: { 31 | titleTemplate: title => `${title} - fixture`, 32 | link: isTesting 33 | ? [] 34 | : [ 35 | { 36 | rel: 'stylesheet', 37 | href: 'https://newcss.net/lite.css', 38 | }, 39 | ], 40 | }, 41 | ...(process.env.GLOBALS === 'true' 42 | ? { 43 | globalName: 'bob', 44 | globals: { 45 | nuxt: globalName => `$my${globalName}`, 46 | }, 47 | } 48 | : {}), 49 | generate: { 50 | dir: isPublic ? 'dist/fixture' : undefined, 51 | crawler: false, 52 | routes, 53 | interval, 54 | }, 55 | router: { 56 | base: isPublic ? '/fixture/' : undefined, 57 | }, 58 | build: { 59 | publicPath: isPublic ? 'fixture' : undefined, 60 | }, 61 | buildModules: [ 62 | process.env.NODE_ENV === 'test' ? require('../..').default : rootDir, 63 | ], 64 | } 65 | -------------------------------------------------------------------------------- /test/e2e/ssr-refs.ts: -------------------------------------------------------------------------------- 1 | import { Selector } from 'testcafe' 2 | import { 3 | navigateTo, 4 | expectOnPage, 5 | expectPathnameToBe, 6 | expectNotOnPage, 7 | } from './helpers' 8 | 9 | // eslint-disable-next-line 10 | fixture`ssrRef` 11 | 12 | test('Shows data on ssr-loaded page', async t => { 13 | await navigateTo('/ssr-ref') 14 | await expectOnPage('ref-only SSR rendered') 15 | await expectOnPage('function-runs SSR or client-side') 16 | await expectOnPage('prefetched-result') 17 | await expectOnPage('on: server') 18 | await expectOnPage('shallow-server') 19 | 20 | await t.click(Selector('a').withText('home')) 21 | await t.click(Selector('a').withText('ssr refs')) 22 | await expectOnPage('ref-only SSR rendered') 23 | await expectOnPage('shallow-client') 24 | }) 25 | 26 | test('Shows appropriate data on client-loaded page', async t => { 27 | await navigateTo('/') 28 | await t.click(Selector('a').withText('ssr refs')) 29 | await expectPathnameToBe('/ssr-ref') 30 | await expectNotOnPage('ref-only SSR rendered') 31 | await expectOnPage('function-runs SSR or client-side') 32 | await expectOnPage('on: client') 33 | await expectOnPage('shallow-client') 34 | }) 35 | 36 | test('Shows SSR data when an ssrRef is defined outside setup', async () => { 37 | await navigateTo('/no-setup') 38 | await expectOnPage('ssrRef-SSR overwritten') 39 | await expectOnPage('async-prefetched async') 40 | }) 41 | 42 | test('Shows client-side only data when an ssrRef is defined outside setup', async t => { 43 | await navigateTo('/') 44 | await t.click(Selector('a').withText('outside of setup')) 45 | await expectNotOnPage('ssrRef-SSR overwritten') 46 | await expectNotOnPage('async-prefetched async') 47 | await expectOnPage('ssrRef-default value') 48 | await expectOnPage('async-default async') 49 | }) 50 | -------------------------------------------------------------------------------- /docs/content/en/ssrPromise.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: ssrPromise 3 | description: '@nuxtjs/composition-api provides a way to use the Vue 3 Composition API with Nuxt-specific features.' 4 | category: Helpers 5 | fullscreen: True 6 | --- 7 | 8 | `ssrPromise` runs a promise on the server and serialises the result as a resolved promise for the client. It needs to be run within the `setup()` function but note that it returns a promise which will require special handling. (For example, you cannot just return a promise from setup and use it in the template.) 9 | 10 | ```ts 11 | import { 12 | defineComponent, 13 | onBeforeMount, 14 | ref, 15 | ssrPromise, 16 | } from '@nuxtjs/composition-api' 17 | 18 | export default defineComponent({ 19 | setup() { 20 | const _promise = ssrPromise(async () => myAsyncFunction()) 21 | const resolvedPromise = ref(null) 22 | 23 | onBeforeMount(async () => { 24 | resolvedPromise.value = await _promise 25 | }) 26 | 27 | return { 28 | // On the server, this will be null until the promise resolves. 29 | // On the client, if server-rendered, this will always be the resolved promise. 30 | resolvedPromise, 31 | } 32 | }, 33 | }) 34 | ``` 35 | 36 | 37 | 38 | Under the hood, `ssrPromise` requires a key to ensure that the ref values match between client and server. If you have added `@nuxtjs/composition-api` to your `buildModules`, this will be done automagically by an injected Babel plugin. If you need to do things differently, you can specify a key manually or add `@nuxtjs/composition-api/babel` to your Babel plugins. 39 | 40 | 41 | 42 | 43 | 44 | At the moment, an `ssrPromise` is only suitable for one-offs, unless you provide your own unique key. See [the `ssrRef` documentation](./ssrRef.md) for more information. 45 | 46 | 47 | -------------------------------------------------------------------------------- /test/fixture/pages/ssr-ref.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 75 | -------------------------------------------------------------------------------- /test/e2e/helpers/index.ts: -------------------------------------------------------------------------------- 1 | import { t, Selector, ClientFunction, RequestLogger } from 'testcafe' 2 | 3 | const port = process.env.PORT || 3000 4 | const url = `http://localhost:${port}` 5 | 6 | export function navigateTo(path: string) { 7 | return t.navigateTo(`${url}${path}`) 8 | } 9 | 10 | export function expectOnPage(text: string) { 11 | const selector = Selector('*').withText(new RegExp(text, 'i')) 12 | return t.expect(selector.visible).ok(`${text} was not found on page`) 13 | } 14 | 15 | export function expectNotOnPage(text: string) { 16 | const selector = Selector('*').withText(new RegExp(text, 'i')) 17 | return t.expect(selector.exists).notOk(`${text} was found on page`) 18 | } 19 | 20 | export const getWindowPathname = ClientFunction(() => window.location.pathname) 21 | 22 | export function expectPathnameToBe(pathname: string) { 23 | return t.expect(getWindowPathname()).eql(pathname) 24 | } 25 | export function getLogger( 26 | filter?: Parameters[0], 27 | options?: Parameters[1] 28 | ) { 29 | const logger = RequestLogger(filter, options) 30 | 31 | async function waitForFirstRequest( 32 | condition: Parameters[0] = () => true 33 | ) { 34 | for (let i = 0; i < 50; i++) { 35 | await t.wait(100) 36 | if (await logger.contains(condition)) return 37 | } 38 | } 39 | 40 | async function expectToBeCalled() { 41 | await waitForFirstRequest() 42 | await t.expect(logger.requests.length).gte(1) 43 | return t 44 | } 45 | 46 | async function expectToBeCalledWith( 47 | condition: Parameters[0] 48 | ) { 49 | await waitForFirstRequest(condition) 50 | await t.expect(await logger.contains(condition)).ok() 51 | return t 52 | } 53 | 54 | return { 55 | logger, 56 | expectToBeCalled, 57 | expectToBeCalledWith, 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /test/e2e/static.ts: -------------------------------------------------------------------------------- 1 | import { Selector } from 'testcafe' 2 | import { navigateTo, expectOnPage, getLogger } from './helpers' 3 | 4 | const apiLogger = getLogger(/posts/) 5 | 6 | const port = process.env.PORT || 3000 7 | 8 | const url = (id: string | number) => 9 | process.env.GENERATE 10 | ? `http://localhost:${port}/posts-${id}.json` 11 | : `http://localhost:${port}/api/posts/${id}` 12 | 13 | // eslint-disable-next-line 14 | fixture`useStatic`.beforeEach(async t => { 15 | await t.addRequestHooks(apiLogger.logger) 16 | apiLogger.logger.clear() 17 | }) 18 | 19 | test('Shows data on ssr-loaded page', async t => { 20 | await navigateTo('/static/1') 21 | await expectOnPage('"id": "1"') 22 | const count = await apiLogger.logger.count(Boolean) 23 | await t.expect(count).eql(0) 24 | 25 | await t.click(Selector('a').withText('home')) 26 | await t.click(Selector('a').withText('static')) 27 | await expectOnPage('"id": "1"') 28 | const newCount = await apiLogger.logger.count(Boolean) 29 | await t.expect(newCount).eql(count) 30 | }) 31 | 32 | test('Shows data on non-generated page', async t => { 33 | await navigateTo('/static/3') 34 | apiLogger.logger.clear() 35 | await t.click(Selector('a').withText('Next')) 36 | await t 37 | .expect(apiLogger.logger.count(Boolean)) 38 | .eql(process.env.GENERATE ? 2 : 1) 39 | }) 40 | 41 | test('Shows appropriate data on client-loaded page', async t => { 42 | await navigateTo('/') 43 | await t.click(Selector('a').withText('static')) 44 | await expectOnPage('"id": "1"') 45 | await t.expect(apiLogger.logger.count(Boolean)).eql(1) 46 | await apiLogger.expectToBeCalledWith(r => r.request.url === url(1)) 47 | 48 | await t.click(Selector('a').withText('Next')) 49 | await expectOnPage('"id": "2"') 50 | await t.expect(apiLogger.logger.count(Boolean)).eql(2) 51 | await apiLogger.expectToBeCalledWith(r => r.request.url === url(2)) 52 | }) 53 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "Code scanning - action" 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | # The branches below must be a subset of the branches above 8 | branches: [main] 9 | schedule: 10 | - cron: "0 5 * * 6" 11 | 12 | jobs: 13 | CodeQL-Build: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Checkout repository 18 | uses: actions/checkout@v2 19 | with: 20 | # We must fetch at least the immediate parents so that if this is 21 | # a pull request then we can checkout the head. 22 | fetch-depth: 2 23 | 24 | # If this run was triggered by a pull request event, then checkout 25 | # the head of the pull request instead of the merge commit. 26 | - run: git checkout HEAD^2 27 | if: ${{ github.event_name == 'pull_request' }} 28 | 29 | # Initializes the CodeQL tools for scanning. 30 | - name: Initialize CodeQL 31 | uses: github/codeql-action/init@v1 32 | 33 | # Override language selection by uncommenting this and choosing your languages 34 | # with: 35 | # languages: go, javascript, csharp, python, cpp, java 36 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 37 | # If this step fails, then you should remove it and run the build manually (see below) 38 | - name: Autobuild 39 | uses: github/codeql-action/autobuild@v1 40 | 41 | # ℹ️ Command-line programs to run using the OS shell. 42 | # 📚 https://git.io/JvXDl 43 | 44 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 45 | # and modify them (or add more) to build your code if your project 46 | # uses a compiled language 47 | 48 | #- run: | 49 | # make bootstrap 50 | # make release 51 | 52 | - name: Perform CodeQL Analysis 53 | uses: github/codeql-action/analyze@v1 54 | -------------------------------------------------------------------------------- /docs/content/en/useStatic.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useStatic 3 | description: '@nuxtjs/composition-api provides a way to use the Vue 3 Composition API with Nuxt-specific features.' 4 | category: Helpers 5 | --- 6 | 7 | You can pre-run expensive functions using `useStatic`. 8 | 9 | ```ts 10 | import { defineComponent, useContext, useStatic, computed } from '@nuxtjs/composition-api' 11 | import axios from 'axios' 12 | 13 | export default defineComponent({ 14 | setup() { 15 | const { params } = useContext() 16 | const id = computed(() => params.value.id) 17 | const post = useStatic( 18 | id => axios.get(`https://jsonplaceholder.typicode.com/posts/${id}`), 19 | id, 20 | 'post' 21 | ) 22 | 23 | return { post } 24 | }, 25 | }) 26 | ``` 27 | 28 | ## SSG 29 | If you are generating the whole app (or just prerendering some routes with `nuxt build && nuxt generate --no-build`) the following behaviour will be unlocked: 30 | 31 | * On generate, the result of a `useStatic` call will be saved to a JSON file and copied into the `/dist` directory. 32 | * On hard-reload of a generated page, the JSON will be inlined into the page and cached. 33 | * On client navigation to a generated page, this JSON will be fetched - and once fetched it will be cached for subsequent navigations. If for whatever reason this JSON doesn't exist, such as if the page *wasn't* pre-generated, the original factory function will be run on client-side. 34 | 35 | 36 | 37 | If you are pregenerating some pages in your app note that you may need to increase `generate.interval`. (See [setup instructions](/setup.html).) 38 | 39 | 40 | 41 | ## SSR 42 | If the route is not pre-generated (including in dev mode), then: 43 | 44 | * On a hard-reload, the server will run the factory function and inline the result in `nuxtState` - so the client won't rerun the API request. The result will be cached between requests. 45 | * On client navigation, the client will run the factory function. 46 | 47 | In both of these cases, the return result of `useStatic` is a `null` ref that is filled with the result of the factory function or JSON fetch when it resolves. 48 | -------------------------------------------------------------------------------- /test/fixture/pages/index.vue: -------------------------------------------------------------------------------- 1 | 45 | 46 | 84 | -------------------------------------------------------------------------------- /docs/content/en/setup.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Quick start 3 | description: '@nuxtjs/composition-api provides a way to use the Vue 3 Composition API with Nuxt-specific features.' 4 | category: Getting started 5 | position: 2 6 | --- 7 | 8 | ## Quick start 9 | 10 | 1. First, install `@nuxtjs/composition-api`: 11 | 12 | 13 | 14 | 15 | ```bash 16 | yarn add @nuxtjs/composition-api 17 | ``` 18 | 19 | 20 | 21 | 22 | ```bash 23 | npm install @nuxtjs/composition-api --save 24 | ``` 25 | 26 | 27 | 28 | 29 | 2. Enable the module. 30 | 31 | ```js[nuxt.config.js] 32 | { 33 | buildModules: [ 34 | '@nuxtjs/composition-api' 35 | ] 36 | } 37 | ``` 38 | 39 | 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. 40 | 41 | 3. **Optional**. Currently [there's an issue with static site generation and async functions](https://github.com/nuxt-community/composition-api/issues/44) which means that you'll need to add time between pages being generated to allow for any async functions to resolve, if you are pre-generating any of your pages: 42 | 43 | ```js[nuxt.config.js] 44 | { 45 | generate: { 46 | // choose to suit your project 47 | interval: 2000, 48 | } 49 | } 50 | ``` 51 | 52 | 4. You're good to go! 53 | 54 | 55 | 56 | 57 | - 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. 58 | 59 | - 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`. 60 | 61 | 62 | 63 | ## Testing with Jest 64 | 65 | If you need to use jest tests with this module installed, just add the following lines to your `jest.config.js`: 66 | ```js[jest.config.js] 67 | moduleNameMapper: { 68 | '@nuxtjs/composition-api': '@nuxtjs/composition-api/lib/cjs/entrypoint.js', 69 | }, 70 | ``` -------------------------------------------------------------------------------- /docs/content/en/ssrRef.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: ssrRef 3 | description: '@nuxtjs/composition-api provides a way to use the Vue 3 Composition API with Nuxt-specific features.' 4 | category: Helpers 5 | fullscreen: True 6 | --- 7 | 8 | When creating composition utility functions, often there will be server-side state that needs to be conveyed to the client. 9 | 10 | `ssrRef` will automatically add ref values to `window.__NUXT__` on SSR if they have been changed from their initial value. It can be used outside of components, such as in shared utility functions, and it supports passing a factory function that will generate the initial value of the ref. 11 | 12 | ```ts 13 | import { ssrRef } from '@nuxtjs/composition-api' 14 | 15 | const val = ssrRef('') 16 | 17 | // When hard-reloaded, `val` will be initialised to 'server set' 18 | if (process.server) val.value = 'server set' 19 | 20 | // When hard-reloaded, the result of myExpensiveSetterFunction() will 21 | // be encoded in nuxtState and used as the initial value of this ref. 22 | // If client-loaded, the setter function will run to come up with initial value. 23 | const val2 = ssrRef(myExpensiveSetterFunction) 24 | ``` 25 | 26 | 27 | 28 | Under the hood, `ssrRef` requires a key to ensure that the ref values match between client and server. If you have added `@nuxtjs/composition-api` to your `buildModules`, this will be done automagically by an injected Babel plugin. If you need to do things differently, you can specify a key manually or add `@nuxtjs/composition-api/babel` to your Babel plugins. 29 | 30 | 31 | 32 | 33 | 34 | At the moment, an `ssrRef` is only suitable for one-offs, unless you provide your own unique key. 35 | 36 | This is because server and client `ssrRef` matches up based on line number within your code. 37 | 38 | ```ts 39 | function useMyFeature() { 40 | // Only one unique key is generated 41 | const feature = ssrRef('') 42 | return feature 43 | } 44 | 45 | const a = useMyFeature() 46 | const b = useMyFeature() 47 | 48 | b.value = 'changed' 49 | // On client-side, a's value will also be initialised to 'changed' 50 | ``` 51 | 52 | If you want to use this pattern, make sure to set a unique key based on each calling of the function. 53 | 54 | 55 | -------------------------------------------------------------------------------- /src/async.ts: -------------------------------------------------------------------------------- 1 | import { isRef, onServerPrefetch } from '@vue/composition-api' 2 | import type { Ref } from '@vue/composition-api' 3 | 4 | import { globalNuxt } from './globals' 5 | import { ssrRef } from './ssr-ref' 6 | import { validateKey } from './utils' 7 | 8 | /** 9 | * You can create reactive values that depend on asynchronous calls with `useAsync`. 10 | * 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. 11 | * 12 | * 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. 13 | * 14 | * **At the moment, `useAsync` is only suitable for one-offs, unless you provide your own unique key.** 15 | * @param cb The async function that will populate the ref this function returns. 16 | * @param key Under the hood, `useAsync` requires a key to ensure that the ref values match between client and server. If you have added `@nuxtjs/composition-api` to your `buildModules`, this will be done automagically by an injected Babel plugin. If you need to do things differently, you can specify a key manually or add `@nuxtjs/composition-api/babel` to your Babel plugins. 17 | * 18 | * @example 19 | ```ts 20 | import { defineComponent, useAsync, computed } from '@nuxtjs/composition-api' 21 | import axios from 'axios' 22 | 23 | export default defineComponent({ 24 | setup() { 25 | const posts = useAsync(() => axios.get('/api/posts')) 26 | 27 | return { posts } 28 | }, 29 | }) 30 | ``` 31 | */ 32 | export const useAsync = ( 33 | cb: () => T | Promise, 34 | key?: string | Ref 35 | ): Ref => { 36 | validateKey(key) 37 | 38 | const _ref = isRef(key) ? key : ssrRef(null, key) 39 | 40 | if ( 41 | !_ref.value || 42 | (process.env.NODE_ENV === 'development' && 43 | process.client && 44 | window[globalNuxt]?.context.isHMR) 45 | ) { 46 | const p = Promise.resolve(cb()) 47 | 48 | if (process.server) { 49 | onServerPrefetch(async () => { 50 | _ref.value = await p 51 | }) 52 | } else { 53 | // eslint-disable-next-line 54 | p.then(res => (_ref.value = res)) 55 | } 56 | } 57 | 58 | return _ref as Ref 59 | } 60 | -------------------------------------------------------------------------------- /test/unit/ssr-ref.spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @jest-environment jsdom 3 | */ 4 | import { ssrRef, setSSRContext } from '../..' 5 | 6 | jest.setTimeout(60000) 7 | 8 | /* eslint-disable @typescript-eslint/no-var-requires */ 9 | const { setup, get } = require('@nuxtjs/module-test-utils') 10 | const config = require('../fixture/nuxt.config') 11 | /* eslint-enable */ 12 | 13 | let nuxt 14 | 15 | describe('ssrRef', () => { 16 | beforeAll(async () => { 17 | nuxt = (await setup(config)).nuxt 18 | }, 60000) 19 | 20 | afterAll(async () => { 21 | await nuxt.close() 22 | }) 23 | 24 | test('__NUXT__ contains correct data', async () => { 25 | const homePage = await get('/') 26 | expect(homePage.includes('"only SSR rendered"')).toBeFalsy() 27 | expect(homePage.includes('"runs SSR or client-side"')).toBeFalsy() 28 | 29 | const ssrRefPage = await get('/ssr-ref') 30 | expect(ssrRefPage).toContain('"only SSR rendered"') 31 | expect(ssrRefPage).toContain('"runs SSR or client-side"') 32 | 33 | const noSetupPage = await get('/no-setup') 34 | expect(noSetupPage).toContain('"prefetched async"') 35 | expect(noSetupPage).toContain('"SSR overwritten"') 36 | expect(noSetupPage.includes('"unchanged"')).toBeFalsy() 37 | 38 | const rerenderedHomePage = await get('/') 39 | expect(rerenderedHomePage.includes('"only SSR rendered"')).toBeFalsy() 40 | expect(rerenderedHomePage.includes('"runs SSR or client-side"')).toBeFalsy() 41 | }) 42 | }) 43 | 44 | describe('ssrRef reactivity', () => { 45 | let ssrContext: Record 46 | 47 | beforeEach(async () => { 48 | ssrContext = Object.assign({}, { nuxt: {} }) 49 | setSSRContext(ssrContext) 50 | }) 51 | test('ssrRefs react to change in state', async () => { 52 | process.client = false 53 | const name = ssrRef('', 'name') 54 | ssrRef('', 'unchanged') 55 | name.value = 'full name' 56 | expect(ssrContext).toMatchSnapshot() 57 | }) 58 | test('ssrRefs react to deep change in object state', async () => { 59 | process.client = false 60 | const obj = ssrRef({ deep: { object: { name: 'nothing' } } }, 'obj') 61 | obj.value.deep.object.name = 'full name' 62 | expect(ssrContext).toMatchSnapshot() 63 | }) 64 | test('ssrRefs react to deep change in array state', async () => { 65 | process.client = false 66 | const obj = ssrRef({ deep: { object: [{ name: 'nothing' }] } }, 'obj') 67 | obj.value.deep.object[0].name = 'full name' 68 | expect(ssrContext).toMatchSnapshot() 69 | }) 70 | }) 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

🏗️ Nuxt Composition API

2 |

Composition API hooks for Nuxt

3 | 4 |

5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |

24 | 25 | > `@nuxtjs/composition-api` provides a way to use the Vue 3 Composition API in with Nuxt-specific features. 26 | 27 | **Note**: the main aim is to allow experimentation and feedback before the final release of Nuxt 3. It is not recommended to use this package in production. 28 | 29 | ## Features 30 | 31 | - 🏃 **Fetch**: Support for the new Nuxt `fetch()` in v2.12+ 32 | - ℹ️ **Context**: Easy access to `router`, `app`, `store` within `setup()` 33 | - 🗺️ **Head**: Interact directly with your `vue-meta` properties within `setup()` 34 | - ✨ **Automatic hydration**: Drop-in replacement for `ref` with automatic SSR stringification and hydration (`ssrRef`) 35 | - 📝 **SSR support**: Allows using the Composition API with SSR 36 | - 💪 **TypeScript**: Written in TypeScript 37 | 38 |

39 | Read Documentation 40 |

41 | 42 | ## Contributors 43 | 44 | Contributions are very welcome. 45 | 46 | 1. Clone this repo 47 | 48 | ```bash 49 | git clone git@github.com:nuxt-community/composition-api.git 50 | ``` 51 | 52 | 2. Install dependencies and build project 53 | 54 | ```bash 55 | yarn 56 | # Compile library and watch for changes 57 | yarn watch 58 | # Start a test Nuxt fixture with hot reloading 59 | yarn fixture 60 | # Test 61 | yarn test 62 | ``` 63 | 64 | **Tip:** You can also use `yarn link` to test the module locally with your Nuxt project. 65 | 66 | ## License 67 | 68 | [MIT License](./LICENCE) - Copyright © Daniel Roe 69 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | - Using welcoming and inclusive language 12 | - Being respectful of differing viewpoints and experiences 13 | - Gracefully accepting constructive criticism 14 | - Focusing on what is best for the community 15 | - Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | - The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | - Trolling, insulting/derogatory comments, and personal or political attacks 21 | - Public or private harassment 22 | - Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | - Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies within all project spaces, and it also applies when an individual is representing the project or its community in public spaces. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [daniel@roe.dev](mailto:daniel@roe.dev). All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 44 | 45 | [homepage]: https://www.contributor-covenant.org 46 | 47 | For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq 48 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { resolve, join } from 'path' 2 | import { readdirSync, copyFileSync, existsSync, mkdirpSync } from 'fs-extra' 3 | 4 | import type { Module } from '@nuxt/types' 5 | import normalize from 'normalize-path' 6 | 7 | const loadUtils = () => { 8 | try { 9 | // Try to load nuxt edge utils first 10 | return require('@nuxt/utils-edge') 11 | } catch { 12 | // if it fails, fall back to normal nuxt utils 13 | return require('@nuxt/utils') 14 | } 15 | } 16 | 17 | const utils = loadUtils() 18 | 19 | const compositionApiModule: Module = function () { 20 | const libRoot = resolve(__dirname, '..') 21 | 22 | let corejsPolyfill 23 | try { 24 | // eslint-disable-next-line 25 | const corejsPkg = require('core-js/package.json') 26 | corejsPolyfill = corejsPkg.version.slice(0, 1) 27 | } catch { 28 | corejsPolyfill = undefined 29 | } 30 | 31 | const { dst: pluginDst } = this.addTemplate({ 32 | src: resolve(libRoot, 'lib', 'plugin.js'), 33 | fileName: join('composition-api', 'plugin.js'), 34 | options: { 35 | corejsPolyfill, 36 | }, 37 | }) 38 | 39 | const staticPath = join(this.options.buildDir || '', 'static-json') 40 | 41 | this.nuxt.hook('generate:route', () => { 42 | mkdirpSync(staticPath) 43 | }) 44 | 45 | this.nuxt.hook('generate:done', async (generator: any) => { 46 | if (!existsSync(staticPath)) return 47 | 48 | const { distPath } = generator 49 | readdirSync(staticPath).forEach(file => 50 | copyFileSync(join(staticPath, file), join(distPath, file)) 51 | ) 52 | }) 53 | 54 | const globalName = this.options.globalName || 'nuxt' 55 | const globalContextFactory = 56 | this.options.globals?.context || 57 | ((globalName: string) => `__${globalName.toUpperCase()}__`) 58 | const globalNuxtFactory = 59 | this.options.globals?.nuxt || ((globalName: string) => `$${globalName}`) 60 | const globalContext = globalContextFactory(globalName) 61 | const globalNuxt = globalNuxtFactory(globalName) 62 | 63 | const { dst: entryDst } = this.addTemplate({ 64 | src: resolve(libRoot, 'lib', 'entrypoint.js'), 65 | fileName: join('composition-api', 'index.js'), 66 | options: { 67 | isFullStatic: 'isFullStatic' in utils && utils.isFullStatic(this.options), 68 | staticPath: normalize(staticPath), 69 | publicPath: normalize(join(this.options.router?.base || '', '/')), 70 | globalContext, 71 | globalNuxt, 72 | }, 73 | }) 74 | 75 | this.options.build = this.options.build || {} 76 | this.options.build.babel = this.options.build.babel || {} 77 | this.options.build.babel.plugins = this.options.build.babel.plugins || [] 78 | if (this.options.build.babel.plugins instanceof Function) { 79 | console.warn( 80 | 'Unable to automatically add Babel plugin. Make sure your custom `build.babel.plugins` returns `@nuxtjs/composition-api/babel`' 81 | ) 82 | } else { 83 | this.options.build.babel.plugins.push(join(__dirname, 'babel')) 84 | } 85 | 86 | this.options.build.transpile = this.options.build.transpile || [] 87 | this.options.build.transpile.push(/@nuxtjs[\\/]composition-api/) 88 | 89 | this.extendBuild(config => { 90 | config.resolve = config.resolve || {} 91 | config.resolve.alias = config.resolve.alias || {} 92 | config.resolve.alias['@nuxtjs/composition-api'] = resolve( 93 | this.options.buildDir || '', 94 | entryDst 95 | ) 96 | }) 97 | 98 | this.options.plugins = this.options.plugins || [] 99 | this.options.plugins.unshift(resolve(this.options.buildDir || '', pluginDst)) 100 | } 101 | 102 | export default compositionApiModule 103 | // eslint-disable-next-line 104 | export const meta = require('../package.json') 105 | 106 | export * from './entrypoint' 107 | -------------------------------------------------------------------------------- /src/meta.ts: -------------------------------------------------------------------------------- 1 | import defu from 'defu' 2 | import Vue from 'vue' 3 | import { 4 | getCurrentInstance, 5 | toRefs, 6 | Ref, 7 | reactive, 8 | watch, 9 | UnwrapRef, 10 | customRef, 11 | set, 12 | } from '@vue/composition-api' 13 | 14 | import type { MetaInfo } from 'vue-meta' 15 | 16 | export type ReactiveHead> = UnwrapRef< 17 | Ref 18 | > 19 | 20 | type MetaInfoMapper = { 21 | [P in keyof T]: P extends 'base' 22 | ? T[P] | undefined 23 | : T[P] extends () => any 24 | ? T[P] | undefined 25 | : T[P] extends Array | Record 26 | ? T[P] 27 | : T[P] | undefined 28 | } 29 | 30 | function assign>(target: T, source: Partial) { 31 | Object.entries(source).forEach(([key, value]) => { 32 | set(target, key, value) 33 | }) 34 | return target 35 | } 36 | 37 | export function createEmptyMeta(): Omit< 38 | MetaInfoMapper>, 39 | 'titleTemplate' 40 | > { 41 | return { 42 | __dangerouslyDisableSanitizers: [], 43 | __dangerouslyDisableSanitizersByTagID: {}, 44 | 45 | title: undefined, 46 | htmlAttrs: {}, 47 | headAttrs: {}, 48 | bodyAttrs: {}, 49 | 50 | base: undefined, 51 | 52 | meta: [], 53 | link: [], 54 | style: [], 55 | script: [], 56 | noscript: [], 57 | 58 | changed: undefined, 59 | afterNavigation: undefined, 60 | } 61 | } 62 | 63 | export const getHeadOptions = (options: { 64 | head: () => Record | Record 65 | }) => { 66 | const _head: ReactiveHead = 67 | options.head instanceof Function 68 | ? reactive({}) 69 | : reactive(options.head) 70 | const head = 71 | options.head instanceof Function 72 | ? () => defu(_head, options.head()) 73 | : () => _head 74 | return { _head, head } 75 | } 76 | 77 | type ToRefs> = { 78 | [P in keyof T]: Ref 79 | } 80 | 81 | /** 82 | * `useMeta` lets you interact directly with [`head()` properties](https://nuxtjs.org/api/pages-head/) in `setup`. **Make sure you set `head: {}` in your component options.** 83 | * @example 84 | ```ts 85 | import { defineComponent, useMeta, computed } from '@nuxtjs/composition-api' 86 | 87 | export default defineComponent({ 88 | head: {}, 89 | setup() { 90 | const { title } = useMeta() 91 | title.value = 'My page' 92 | }) 93 | }) 94 | ``` 95 | * @param init Whatever defaults you want to set for `head` properties. 96 | */ 97 | export const useMeta = (init?: T) => { 98 | const vm = getCurrentInstance() 99 | if (!vm) throw new Error('useMeta must be called within a component.') 100 | 101 | if (!('_head' in vm.$options)) 102 | throw new Error( 103 | '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.' 104 | ) 105 | 106 | const { _head = reactive({}) as ReactiveHead } = vm.$options 107 | 108 | assign(_head, createEmptyMeta()) 109 | assign(_head, init || {}) 110 | 111 | const refs = toRefs(_head) as ToRefs< 112 | ReturnType & { 113 | titleTemplate: ReactiveHead['titleTemplate'] 114 | } & T 115 | > 116 | 117 | refs.titleTemplate = customRef( 118 | (track, trigger) => { 119 | return { 120 | get() { 121 | track() 122 | return _head.titleTemplate 123 | }, 124 | set(newValue) { 125 | if (!_head.titleTemplate) { 126 | set(_head, 'titleTemplate', newValue) 127 | } else { 128 | _head.titleTemplate = newValue 129 | } 130 | trigger() 131 | }, 132 | } 133 | } 134 | ) 135 | 136 | if (process.client) 137 | watch(Object.values(refs), vm.$meta().refresh, { immediate: true }) 138 | 139 | return refs 140 | } 141 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@nuxtjs/composition-api", 3 | "version": "0.12.5", 4 | "description": "Composition API hooks for Nuxt", 5 | "license": "MIT", 6 | "repository": "nuxt-community/composition-api", 7 | "author": { 8 | "name": "Daniel Roe", 9 | "email": "daniel@roe.dev", 10 | "url": "https://roe.dev" 11 | }, 12 | "keywords": [ 13 | "vue", 14 | "vuejs", 15 | "nuxt", 16 | "composition-api", 17 | "typescript", 18 | "javascript" 19 | ], 20 | "main": "lib/index.js", 21 | "types": "lib/index.d.ts", 22 | "files": [ 23 | "lib/**/*", 24 | "lib/index.d.ts", 25 | "!**/*.map" 26 | ], 27 | "scripts": { 28 | "build": "yarn clean && yarn compile", 29 | "watch": "yarn compile -w", 30 | "clean": "rm -rf lib/*", 31 | "clean:fixture": "rm -rf dist/ test/fixture/.nuxt", 32 | "compile": "rollup -c", 33 | "fixture": "cross-env CMD=dev yarn nuxt-run", 34 | "fixture:prod": "yarn clean:fixture && cross-env CMD=build yarn nuxt-run && cross-env CMD=start yarn nuxt-run", 35 | "fixture:generate:export": "yarn clean:fixture && cross-env GENERATE=true PORT=6000 CMD=generate yarn nuxt-run", 36 | "fixture:generate": "yarn fixture:generate:export && yarn http-server -s -p 5000 dist", 37 | "lint": "run-s lint:all:*", 38 | "lint:all:eslint": "yarn lint:eslint --ext .js,.ts,.vue .", 39 | "lint:all:prettier": "yarn lint:prettier \"**/*.{js,json,ts,vue}\"", 40 | "lint:eslint": "eslint --fix", 41 | "lint:prettier": "prettier --write --loglevel warn", 42 | "now-build": "NOW_BUILD=true yarn fixture:generate:export", 43 | "nuxt-run": "cross-env-shell \"yarn nuxt $CMD -c test/fixture/nuxt.config.js\"", 44 | "prepare": "yarn build", 45 | "prepublishOnly": "yarn lint && yarn test", 46 | "release": "release-it", 47 | "test": "run-s test:*", 48 | "test:e2e-globals": "cross-env GLOBALS=true PORT=3000 start-server-and-test fixture:prod 3000 \"testcafe firefox:headless test/e2e\"", 49 | "test:e2e-ssr": "cross-env PORT=4000 start-server-and-test fixture:prod 4000 \"testcafe firefox:headless test/e2e\"", 50 | "test:e2e-generated": "cross-env GENERATE=true PORT=5000 start-server-and-test fixture:generate 5000 \"testcafe -q firefox:headless test/e2e\"", 51 | "test:types": "tsd", 52 | "test:unit": "jest" 53 | }, 54 | "tsd": { 55 | "directory": "test/tsd", 56 | "compilerOptions": { 57 | "rootDir": "." 58 | } 59 | }, 60 | "gitHooks": { 61 | "pre-commit": "lint-staged" 62 | }, 63 | "dependencies": { 64 | "@vue/composition-api": "1.0.0-beta.16", 65 | "defu": "^3.1.0", 66 | "normalize-path": "^3.0.0" 67 | }, 68 | "devDependencies": { 69 | "@babel/plugin-transform-runtime": "^7.11.5", 70 | "@babel/preset-env": "^7.11.5", 71 | "@babel/preset-typescript": "^7.10.4", 72 | "@nuxt/types": "^2.14.6", 73 | "@nuxtjs/module-test-utils": "^1.6.3", 74 | "@release-it/conventional-changelog": "^2.0.0", 75 | "@types/jest": "^26.0.14", 76 | "@types/normalize-path": "^3.0.0", 77 | "@typescript-eslint/eslint-plugin": "^4.1.1", 78 | "@typescript-eslint/parser": "^4.1.1", 79 | "babel-loader": "^8.1.0", 80 | "codecov": "^3.7.2", 81 | "cross-env": "^7.0.2", 82 | "eslint": "^7.9.0", 83 | "eslint-config-prettier": "^6.11.0", 84 | "eslint-plugin-jest": "^24.0.2", 85 | "eslint-plugin-prettier": "^3.1.4", 86 | "eslint-plugin-promise": "^4.2.1", 87 | "http-server": "^0.12.3", 88 | "jest": "^26.4.2", 89 | "lint-staged": "^10.4.0", 90 | "npm-run-all": "^4.1.5", 91 | "nuxt": "^2.14.6", 92 | "prettier": "^2.1.2", 93 | "release-it": "14.0.3", 94 | "rollup": "^2.28.1", 95 | "rollup-plugin-copy": "^3.3.0", 96 | "rollup-plugin-dts": "^1.4.13", 97 | "rollup-plugin-typescript2": "^0.27.2", 98 | "start-server-and-test": "^1.11.4", 99 | "testcafe": "1.9.3", 100 | "tsd": "^0.13.1", 101 | "typescript": "4", 102 | "yorkie": "^2.0.0" 103 | }, 104 | "peerDependencies": { 105 | "@nuxt/vue-app": "^2.14.6", 106 | "nuxt": "^2.14.6", 107 | "vue": "^2" 108 | }, 109 | "volta": { 110 | "node": "12.18.4" 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/static.ts: -------------------------------------------------------------------------------- 1 | import { ssrRef } from './ssr-ref' 2 | import { onServerPrefetch, watch, computed, ref } from '@vue/composition-api' 3 | import type { Ref } from '@vue/composition-api' 4 | 5 | const staticPath = '<%= options.staticPath %>' 6 | const staticCache: Record = {} 7 | 8 | function writeFile(key: string, value: string) { 9 | if (process.client || !process.static) return 10 | 11 | const { writeFileSync }: typeof import('fs') = process.client 12 | ? '' 13 | : require('fs') 14 | const { join }: typeof import('path') = process.client ? '' : require('path') 15 | 16 | try { 17 | writeFileSync(join(staticPath, `${key}.json`), value) 18 | } catch (e) { 19 | console.log(e) 20 | } 21 | } 22 | /** 23 | * You can pre-run expensive functions using `useStatic`. 24 | * 25 | * __SSG__ 26 | * If you are generating the whole app (or just prerendering some routes with `nuxt build && nuxt generate --no-build`) the following behaviour will be unlocked: 27 | 28 | 1. On generate, the result of a `useStatic` call will be saved to a JSON file and copied into the `/dist` directory. 29 | 2. On hard-reload of a generated page, the JSON will be inlined into the page and cached. 30 | 3. On client navigation to a generated page, this JSON will be fetched - and once fetched it will be cached for subsequent navigations. If for whatever reason this JSON doesn't exist, such as if the page *wasn't* pre-generated, the original factory function will be run on client-side. 31 | 32 | If you are pregenerating some pages in your app note that you may need to increase `generate.interval`. (See [setup instructions](https://composition-api.nuxtjs.org/setup.html).) 33 | 34 | * 35 | * __SSR__ 36 | * If the route is not pre-generated (including in dev mode), then: 37 | 38 | 1. On a hard-reload, the server will run the factory function and inline the result in `nuxtState` - so the client won't rerun the API request. The result will be cached between requests. 39 | 2. On client navigation, the client will run the factory function. 40 | 41 | In both of these cases, the return result of `useStatic` is a `null` ref that is filled with the result of the factory function or JSON fetch when it resolves. 42 | 43 | * @param factory The async function that will populate the ref this function returns. It receives the param and keyBase (see below) as parameters. 44 | * @param param A an optional param (such as an ID) to distinguish multiple API fetches using the same factory function. 45 | * @param keyBase A key that should be unique across your project. If not provided, this will be auto-generated by `@nuxtjs/composition-api`. 46 | * @example 47 | ```ts 48 | import { defineComponent, useContext, useStatic, computed } from '@nuxtjs/composition-api' 49 | import axios from 'axios' 50 | 51 | export default defineComponent({ 52 | setup() { 53 | const { params } = useContext() 54 | const id = computed(() => params.value.id) 55 | const post = useStatic( 56 | id => axios.get(`https://jsonplaceholder.typicode.com/posts/${id}`), 57 | id, 58 | 'post' 59 | ) 60 | 61 | return { post } 62 | }, 63 | }) 64 | ``` 65 | */ 66 | export const useStatic = ( 67 | factory: (param: string, key: string) => Promise, 68 | param: Ref = ref(''), 69 | keyBase: string 70 | ): Ref => { 71 | const key = computed(() => `${keyBase}-${param.value}`) 72 | const result = ssrRef(null, key.value) 73 | 74 | if (result.value) staticCache[key.value] = result.value 75 | 76 | if (process.client) { 77 | const onFailure = () => 78 | factory(param.value, key.value).then(r => { 79 | staticCache[key.value] = r 80 | result.value = r 81 | return 82 | }) 83 | watch( 84 | key, 85 | key => { 86 | if (key in staticCache) { 87 | result.value = staticCache[key] 88 | return 89 | } 90 | /* eslint-disable promise/always-return */ 91 | if (!process.static) onFailure() 92 | else 93 | fetch(`<%= options.publicPath %>${key}.json`) 94 | .then(response => { 95 | if (!response.ok) throw new Error('Response invalid.') 96 | return response.json() 97 | }) 98 | .then(json => { 99 | staticCache[key] = json 100 | result.value = json 101 | }) 102 | .catch(onFailure) 103 | /* eslint-enable */ 104 | }, 105 | { 106 | immediate: true, 107 | } 108 | ) 109 | } else { 110 | if (key.value in staticCache) { 111 | result.value = staticCache[key.value] 112 | return result as Ref 113 | } 114 | onServerPrefetch(async () => { 115 | const [_key, _param] = [key.value, param.value] 116 | 117 | result.value = await factory(_param, _key) 118 | staticCache[_key] = result.value 119 | writeFile(_key, JSON.stringify(result.value)) 120 | }) 121 | } 122 | 123 | return result as Ref 124 | } 125 | -------------------------------------------------------------------------------- /src/ssr-ref.ts: -------------------------------------------------------------------------------- 1 | import { 2 | customRef, 3 | onServerPrefetch, 4 | ref, 5 | shallowRef, 6 | } from '@vue/composition-api' 7 | import type { Ref } from '@vue/composition-api' 8 | 9 | import { globalContext, globalNuxt } from './globals' 10 | import { validateKey } from './utils' 11 | 12 | function getValue(value: T | (() => T)): T { 13 | if (value instanceof Function) return value() 14 | return value 15 | } 16 | 17 | let data: any = {} 18 | 19 | export function setSSRContext(ssrContext: any) { 20 | data = Object.assign({}, {}) 21 | ssrContext.nuxt.ssrRefs = data 22 | } 23 | 24 | const isProxyable = (val: unknown): val is Record => 25 | val && typeof val === 'object' 26 | 27 | const sanitise = (val: unknown) => 28 | (val && JSON.parse(JSON.stringify(val))) || val 29 | 30 | const ssrValue = (value: T | (() => T), key: string): T => { 31 | if (process.client) { 32 | if ( 33 | process.env.NODE_ENV === 'development' && 34 | window[globalNuxt]?.context.isHMR 35 | ) { 36 | return getValue(value) 37 | } 38 | return (window as any)[globalContext]?.ssrRefs?.[key] ?? getValue(value) 39 | } 40 | return getValue(value) 41 | } 42 | 43 | /** 44 | * `ssrRef` will automatically add ref values to `window.__NUXT__` on SSR if they have been changed from their initial value. It can be used outside of components, such as in shared utility functions, and it supports passing a factory function that will generate the initial value of the ref. **At the moment, an `ssrRef` is only suitable for one-offs, unless you provide your own unique key.** 45 | * @param value This can be an initial value or a factory function that will be executed on server-side to get the initial value. 46 | * @param key Under the hood, `ssrRef` requires a key to ensure that the ref values match between client and server. If you have added `@nuxtjs/composition-api` to your `buildModules`, this will be done automagically by an injected Babel plugin. If you need to do things differently, you can specify a key manually or add `@nuxtjs/composition-api/babel` to your Babel plugins. 47 | * @example 48 | ```ts 49 | import { ssrRef } from '@nuxtjs/composition-api' 50 | 51 | const val = ssrRef('') 52 | 53 | // When hard-reloaded, `val` will be initialised to 'server set' 54 | if (process.server) val.value = 'server set' 55 | 56 | // When hard-reloaded, the result of myExpensiveSetterFunction() will 57 | // be encoded in nuxtState and used as the initial value of this ref. 58 | // If client-loaded, the setter function will run to come up with initial value. 59 | const val2 = ssrRef(myExpensiveSetterFunction) 60 | ``` 61 | */ 62 | export const ssrRef = (value: T | (() => T), key?: string): Ref => { 63 | validateKey(key) 64 | let val = ssrValue(value, key) 65 | 66 | if (process.client) return ref(val) as Ref 67 | 68 | if (value instanceof Function) data[key] = sanitise(val) 69 | 70 | const getProxy = >( 71 | track: () => void, 72 | trigger: () => void, 73 | observable: T 74 | ): T => 75 | new Proxy(observable, { 76 | get(target, prop: string | number) { 77 | track() 78 | if (isProxyable(target[prop])) 79 | return getProxy(track, trigger, target[prop]) 80 | return Reflect.get(target, prop) 81 | }, 82 | set(obj, prop, newVal) { 83 | const result = Reflect.set(obj, prop, newVal) 84 | data[key] = sanitise(val) 85 | trigger() 86 | return result 87 | }, 88 | }) 89 | 90 | const proxy = customRef((track, trigger) => ({ 91 | get: () => { 92 | track() 93 | if (isProxyable(val)) return getProxy(track, trigger, val) 94 | return val 95 | }, 96 | set: (v: T) => { 97 | data[key] = sanitise(v) 98 | val = v 99 | trigger() 100 | }, 101 | })) 102 | 103 | return proxy 104 | } 105 | 106 | /** 107 | * This helper creates a [`shallowRef`](https://vue-composition-api-rfc.netlify.app/api.html#shallowref) (a ref that tracks its own .value mutation but doesn't make its value reactive) that is synced between client & server. 108 | * @param value This can be an initial value or a factory function that will be executed on server-side to get the initial value. 109 | * @param key Under the hood, `shallowSsrRef` requires a key to ensure that the ref values match between client and server. If you have added `@nuxtjs/composition-api` to your `buildModules`, this will be done automagically by an injected Babel plugin. If you need to do things differently, you can specify a key manually or add `@nuxtjs/composition-api/babel` to your Babel plugins. 110 | 111 | * @example 112 | ```ts 113 | import { shallowSsrRef, onMounted } from '@nuxtjs/composition-api' 114 | 115 | const shallow = shallowSsrRef({ v: 'init' }) 116 | if (process.server) shallow.value = { v: 'changed' } 117 | 118 | // On client-side, shallow.value will be { v: changed } 119 | onMounted(() => { 120 | // This and other changes outside of setup won't trigger component updates. 121 | shallow.value.v = 'Hello World' 122 | }) 123 | ``` 124 | */ 125 | export const shallowSsrRef = ( 126 | value: T | (() => T), 127 | key?: string 128 | ): Ref => { 129 | validateKey(key) 130 | 131 | if (process.client) return shallowRef(ssrValue(value, key)) 132 | 133 | const _val = getValue(value) 134 | 135 | if (value instanceof Function) { 136 | data[key] = sanitise(_val) 137 | } 138 | 139 | return customRef((track, trigger) => ({ 140 | get() { 141 | track() 142 | return _val 143 | }, 144 | set(newValue: T) { 145 | data[key] = sanitise(newValue) 146 | value = newValue 147 | trigger() 148 | }, 149 | })) 150 | } 151 | 152 | /** 153 | * `ssrPromise` runs a promise on the server and serialises the result as a resolved promise for the client. It needs to be run within the `setup()` function but note that it returns a promise which will require special handling. (For example, you cannot just return a promise from setup and use it in the template.) 154 | * @param value This can be an initial value or a factory function that will be executed on server-side to get the initial value. 155 | * @param key Under the hood, `ssrPromise` requires a key to ensure that the ref values match between client and server. If you have added `@nuxtjs/composition-api` to your `buildModules`, this will be done automagically by an injected Babel plugin. If you need to do things differently, you can specify a key manually or add `@nuxtjs/composition-api/babel` to your Babel plugins. 156 | * @example 157 | 158 | ```ts 159 | import { 160 | defineComponent, 161 | onBeforeMount, 162 | ref, 163 | ssrPromise, 164 | } from '@nuxtjs/composition-api' 165 | 166 | export default defineComponent({ 167 | setup() { 168 | const _promise = ssrPromise(async () => myAsyncFunction()) 169 | const resolvedPromise = ref(null) 170 | 171 | onBeforeMount(async () => { 172 | resolvedPromise.value = await _promise 173 | }) 174 | 175 | return { 176 | // On the server, this will be null until the promise resolves. 177 | // On the client, if server-rendered, this will always be the resolved promise. 178 | resolvedPromise, 179 | } 180 | }, 181 | }) 182 | ``` 183 | */ 184 | export const ssrPromise = ( 185 | value: () => Promise, 186 | key?: string 187 | ): Promise => { 188 | validateKey(key) 189 | 190 | const val = ssrValue(value, key) 191 | if (process.client) return Promise.resolve(val) 192 | 193 | onServerPrefetch(async () => { 194 | data[key] = sanitise(await val) 195 | }) 196 | return val 197 | } 198 | -------------------------------------------------------------------------------- /src/fetch.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import { 3 | getCurrentInstance, 4 | isRef, 5 | onBeforeMount, 6 | onServerPrefetch, 7 | set, 8 | } from '@vue/composition-api' 9 | 10 | import { globalContext, globalNuxt, isFullStatic } from './globals' 11 | import type { NuxtApp } from '@nuxt/types/app' 12 | 13 | type ComponentInstance = NonNullable> 14 | 15 | function normalizeError(err: any) { 16 | let message 17 | if (!(err.message || typeof err === 'string')) { 18 | try { 19 | message = JSON.stringify(err, null, 2) 20 | } catch (e) { 21 | message = `[${err.constructor.name}]` 22 | } 23 | } else { 24 | message = err.message || err 25 | } 26 | return { 27 | ...err, 28 | message, 29 | statusCode: 30 | err.statusCode || 31 | err.status || 32 | (err.response && err.response.status) || 33 | 500, 34 | } 35 | } 36 | 37 | interface Fetch { 38 | (context: ComponentInstance): void | Promise 39 | } 40 | 41 | const fetches = new WeakMap() 42 | const fetchPromises = new Map>() 43 | 44 | const isSsrHydration = (vm: ComponentInstance) => 45 | (vm.$vnode?.elm as any)?.dataset?.fetchKey 46 | const nuxtState = process.client && (window as any)[globalContext] 47 | 48 | interface AugmentedComponentInstance extends ComponentInstance { 49 | _fetchKey?: number 50 | _data?: any 51 | _hydrated?: boolean 52 | _fetchDelay?: number 53 | _fetchOnServer?: boolean 54 | } 55 | 56 | interface AugmentedNuxtApp extends NuxtApp { 57 | isPreview?: boolean 58 | _payloadFetchIndex?: number 59 | _pagePayload?: any 60 | } 61 | 62 | function registerCallback(vm: ComponentInstance, callback: Fetch) { 63 | const callbacks = fetches.get(vm) || [] 64 | fetches.set(vm, [...callbacks, callback]) 65 | } 66 | 67 | async function callFetches(this: AugmentedComponentInstance) { 68 | const fetchesToCall = fetches.get(this) 69 | if (!fetchesToCall) return 70 | ;(this[globalNuxt] as any).nbFetching++ 71 | 72 | this.$fetchState.pending = true 73 | this.$fetchState.error = null 74 | this._hydrated = false 75 | 76 | let error = null 77 | const startTime = Date.now() 78 | 79 | try { 80 | await Promise.all( 81 | fetchesToCall.map(fetch => { 82 | if (fetchPromises.has(fetch)) return fetchPromises.get(fetch) 83 | const promise = Promise.resolve(fetch(this)).finally(() => 84 | fetchPromises.delete(fetch) 85 | ) 86 | fetchPromises.set(fetch, promise) 87 | return promise 88 | }) 89 | ) 90 | } catch (err) { 91 | error = normalizeError(err) 92 | } 93 | 94 | const delayLeft = (this._fetchDelay || 0) - (Date.now() - startTime) 95 | if (delayLeft > 0) { 96 | await new Promise(resolve => setTimeout(resolve, delayLeft)) 97 | } 98 | 99 | this.$fetchState.error = error 100 | this.$fetchState.pending = false 101 | this.$fetchState.timestamp = Date.now() 102 | 103 | this.$nextTick(() => (this[globalNuxt] as any).nbFetching--) 104 | } 105 | 106 | const setFetchState = (vm: AugmentedComponentInstance) => { 107 | vm.$fetchState = 108 | vm.$fetchState || 109 | Vue.observable({ 110 | error: null, 111 | pending: false, 112 | timestamp: 0, 113 | }) 114 | } 115 | 116 | const loadFullStatic = (vm: AugmentedComponentInstance) => { 117 | // Check if component has been fetched on server 118 | const { fetchOnServer } = vm.$options 119 | const fetchedOnServer = 120 | typeof fetchOnServer === 'function' 121 | ? fetchOnServer.call(vm) !== false 122 | : fetchOnServer !== false 123 | 124 | const nuxt = vm.$nuxt as AugmentedNuxtApp 125 | if (!fetchedOnServer || nuxt?.isPreview || !nuxt?._pagePayload) { 126 | return 127 | } 128 | vm._hydrated = true 129 | nuxt._payloadFetchIndex = (nuxt._payloadFetchIndex || 0) + 1 130 | vm._fetchKey = nuxt._payloadFetchIndex 131 | const data = nuxt._pagePayload.fetch[vm._fetchKey] 132 | 133 | // If fetch error 134 | if (data && data._error) { 135 | vm.$fetchState.error = data._error 136 | return 137 | } 138 | 139 | // Merge data 140 | for (const key in data) { 141 | set(vm.$data, key, data[key]) 142 | } 143 | } 144 | 145 | async function serverPrefetch(vm: AugmentedComponentInstance) { 146 | if (!vm._fetchOnServer) return 147 | 148 | // Call and await on $fetch 149 | setFetchState(vm) 150 | 151 | try { 152 | await callFetches.call(vm) 153 | } catch (err) { 154 | vm.$fetchState.error = normalizeError(err) 155 | } 156 | vm.$fetchState.pending = false 157 | 158 | // Define an ssrKey for hydration 159 | vm._fetchKey = vm.$ssrContext.nuxt.fetch.length 160 | 161 | // Add data-fetch-key on parent element of Component 162 | if (!vm.$vnode.data) vm.$vnode.data = {} 163 | const attrs = (vm.$vnode.data.attrs = vm.$vnode.data.attrs || {}) 164 | attrs['data-fetch-key'] = vm._fetchKey 165 | 166 | const data = { ...vm._data } 167 | Object.entries((vm as any).__composition_api_state__.rawBindings).forEach( 168 | ([key, val]) => { 169 | if (val instanceof Function || val instanceof Promise) return 170 | 171 | data[key] = isRef(val) ? val.value : val 172 | } 173 | ) 174 | 175 | // Add to ssrContext for window.__NUXT__.fetch 176 | vm.$ssrContext.nuxt.fetch.push( 177 | vm.$fetchState.error 178 | ? { _error: vm.$fetchState.error } 179 | : JSON.parse(JSON.stringify(data)) 180 | ) 181 | } 182 | 183 | /** 184 | * Versions of Nuxt newer than v2.12 support a [custom hook called `fetch`](https://nuxtjs.org/api/pages-fetch/) that allows server-side and client-side asynchronous data-fetching. 185 | 186 | * @param callback The async function you want to run. 187 | * @example 188 | 189 | ```ts 190 | import { defineComponent, ref, useFetch } from '@nuxtjs/composition-api' 191 | import axios from 'axios' 192 | 193 | export default defineComponent({ 194 | setup() { 195 | const name = ref('') 196 | 197 | const { fetch, fetchState } = useFetch(async () => { 198 | name.value = await axios.get('https://myapi.com/name') 199 | }) 200 | 201 | // Manually trigger a refetch 202 | fetch() 203 | 204 | // Access fetch error, pending and timestamp 205 | fetchState 206 | 207 | return { name } 208 | }, 209 | }) 210 | ``` 211 | */ 212 | export const useFetch = (callback: Fetch) => { 213 | const vm = getCurrentInstance() as AugmentedComponentInstance | undefined 214 | if (!vm) throw new Error('This must be called within a setup function.') 215 | 216 | registerCallback(vm, callback) 217 | 218 | if (typeof vm.$options.fetchOnServer === 'function') { 219 | vm._fetchOnServer = vm.$options.fetchOnServer.call(vm) !== false 220 | } else { 221 | vm._fetchOnServer = vm.$options.fetchOnServer !== false 222 | } 223 | 224 | setFetchState(vm) 225 | 226 | onServerPrefetch(() => serverPrefetch(vm)) 227 | 228 | function result() { 229 | return { 230 | fetch: vm!.$fetch, 231 | fetchState: vm!.$fetchState, 232 | $fetch: vm!.$fetch, 233 | $fetchState: vm!.$fetchState, 234 | } 235 | } 236 | 237 | vm._fetchDelay = 238 | typeof vm.$options.fetchDelay === 'number' ? vm.$options.fetchDelay : 200 239 | 240 | vm.$fetch = callFetches.bind(vm) 241 | 242 | onBeforeMount(() => !vm._hydrated && callFetches.call(vm)) 243 | 244 | if (process.server || !isSsrHydration(vm)) { 245 | if (isFullStatic) onBeforeMount(() => loadFullStatic(vm)) 246 | return result() 247 | } 248 | 249 | // Hydrate component 250 | vm._hydrated = true 251 | vm._fetchKey = +(vm.$vnode.elm as any)?.dataset.fetchKey 252 | const data = nuxtState.fetch[vm._fetchKey] 253 | 254 | // If fetch error 255 | if (data && data._error) { 256 | vm.$fetchState.error = data._error 257 | return result() 258 | } 259 | 260 | onBeforeMount(() => { 261 | // Merge data 262 | for (const key in data) { 263 | try { 264 | if (key in vm && typeof vm[key as keyof typeof vm] === 'function') { 265 | continue 266 | } 267 | set(vm, key, data[key]) 268 | } catch (e) { 269 | if (process.env.NODE_ENV === 'development') 270 | // eslint-disable-next-line 271 | console.warn(`Could not hydrate ${key}.`) 272 | } 273 | } 274 | }) 275 | 276 | return result() 277 | } 278 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### [0.12.5](https://github.com/nuxt-community/composition-api/compare/0.12.4...0.12.5) (2020-09-18) 2 | 3 | ### [0.12.4](https://github.com/nuxt-community/composition-api/compare/0.12.3...0.12.4) (2020-09-16) 4 | 5 | 6 | ### Bug Fixes 7 | 8 | * remove `markReactive` export ([#239](https://github.com/nuxt-community/composition-api/issues/239)) ([d455d0f](https://github.com/nuxt-community/composition-api/commit/d455d0f87e5a798403ffb7bb0263f4e30de0c4ce)), closes [#238](https://github.com/nuxt-community/composition-api/issues/238) 9 | 10 | ### [0.12.3](https://github.com/nuxt-community/composition-api/compare/0.12.2...0.12.3) (2020-08-26) 11 | 12 | 13 | ### Bug Fixes 14 | 15 | * load nuxt edge utils if available ([#219](https://github.com/nuxt-community/composition-api/issues/219)) ([563bb3c](https://github.com/nuxt-community/composition-api/commit/563bb3c33fb7bfc9d5a57353b006c8cc8f268eb0)) 16 | * overwrite titleTemplate with undefined ([#220](https://github.com/nuxt-community/composition-api/issues/220)) ([dc6f253](https://github.com/nuxt-community/composition-api/commit/dc6f25360554aab4cb29ec8d0f021939c5660168)) 17 | 18 | ### [0.12.2](https://github.com/nuxt-community/composition-api/compare/0.12.1...0.12.2) (2020-08-18) 19 | 20 | 21 | ### Bug Fixes 22 | 23 | * use `Promise.resolve()` to cover all possible promise cases ([#211](https://github.com/nuxt-community/composition-api/issues/211)) ([6d10312](https://github.com/nuxt-community/composition-api/commit/6d10312f65cefb219c00bbceb946d3547abc25f4)), closes [#210](https://github.com/nuxt-community/composition-api/issues/210) 24 | 25 | ### [0.12.1](https://github.com/nuxt-community/composition-api/compare/0.12.0...0.12.1) (2020-08-16) 26 | 27 | ## [0.12.0](https://github.com/nuxt-community/composition-api/compare/0.11.0...0.12.0) (2020-08-09) 28 | 29 | 30 | ### Features 31 | 32 | * add new exports from `@vue/composition-api` ([5bbcfd7](https://github.com/nuxt-community/composition-api/commit/5bbcfd75e241e3aa97d07ea87595b171019eec4c)) 33 | 34 | ## [0.11.0](https://github.com/nuxt-community/composition-api/compare/0.10.6...0.11.0) (2020-07-27) 35 | 36 | 37 | ### Features 38 | 39 | * add nuxt helpers ([#160](https://github.com/nuxt-community/composition-api/issues/160)) ([8a9885e](https://github.com/nuxt-community/composition-api/commit/8a9885e5a78dc3c07c38134e45887185be7d6139)) 40 | 41 | ### [0.10.6](https://github.com/nuxt-community/composition-api/compare/0.10.5...0.10.6) (2020-07-14) 42 | 43 | 44 | ### Bug Fixes 45 | 46 | * add support for windows ([#140](https://github.com/nuxt-community/composition-api/issues/140)) ([c39773f](https://github.com/nuxt-community/composition-api/commit/c39773fcff157169e2c7761ca7bbd229bf089331)) 47 | * correctly check for `isFullStatic` ([db87441](https://github.com/nuxt-community/composition-api/commit/db874411b9195d2d90fa1710fb9f4065a0fb153c)) 48 | 49 | ### [0.10.5](https://github.com/nuxt-community/composition-api/compare/0.10.4...0.10.5) (2020-07-09) 50 | 51 | 52 | ### Bug Fixes 53 | 54 | * create `static-json` dir at correct point in generate lifecycle ([2d4e10b](https://github.com/nuxt-community/composition-api/commit/2d4e10b645f982034a0e69a2d3ba338af9c0a255)) 55 | * test for nuxt instance before loading static ([43d5ee6](https://github.com/nuxt-community/composition-api/commit/43d5ee6a18482b981365d3e1669146e96420fcae)), closes [#143](https://github.com/nuxt-community/composition-api/issues/143) 56 | 57 | ### [0.10.4](https://github.com/nuxt-community/composition-api/compare/0.10.3...0.10.4) (2020-07-09) 58 | 59 | 60 | ### Bug Fixes 61 | 62 | * revert to custom ssr fetch handling ([6cde3df](https://github.com/nuxt-community/composition-api/commit/6cde3dfe4555f5bee6a2944d79715c4a705da496)), closes [#133](https://github.com/nuxt-community/composition-api/issues/133) [#141](https://github.com/nuxt-community/composition-api/issues/141) [#143](https://github.com/nuxt-community/composition-api/issues/143) 63 | 64 | ### [0.10.3](https://github.com/nuxt-community/composition-api/compare/0.10.2...0.10.3) (2020-07-01) 65 | 66 | 67 | ### Bug Fixes 68 | 69 | * check static path exists before copying files ([9f1bdc7](https://github.com/nuxt-community/composition-api/commit/9f1bdc7026336e018bec1f6b7d4b63713c451897)) 70 | 71 | ### [0.10.2](https://github.com/nuxt-community/composition-api/compare/0.10.1...0.10.2) (2020-06-25) 72 | 73 | 74 | ### Bug Fixes 75 | 76 | * remove `console.log` ([0b1d5a8](https://github.com/nuxt-community/composition-api/commit/0b1d5a8872760f28e4e7deb030cc6640b1b611cd)) 77 | 78 | ### [0.10.1](https://github.com/nuxt-community/composition-api/compare/0.10.0...0.10.1) (2020-06-25) 79 | 80 | 81 | ### Features 82 | 83 | * export `nextTick` from `@vue/composition-api` ([df68e0a](https://github.com/nuxt-community/composition-api/commit/df68e0a3788dbfca3d3f400465228daf3d202851)) 84 | 85 | ## [0.10.0](https://github.com/nuxt-community/composition-api/compare/0.9.3...0.10.0) (2020-06-25) 86 | 87 | 88 | ### ⚠ BREAKING CHANGES 89 | 90 | * requires nuxt 2.13+ for `useFetch` 91 | 92 | ### Features 93 | 94 | * support static target for `useFetch` ([74205b5](https://github.com/nuxt-community/composition-api/commit/74205b5198e46f43f77601a9c786b49b1e6e9dc1)), closes [#127](https://github.com/nuxt-community/composition-api/issues/127) 95 | 96 | 97 | ### Bug Fixes 98 | 99 | * handle preexisting babel plugins function ([c5c338f](https://github.com/nuxt-community/composition-api/commit/c5c338f1675a05264ce3481e8469ad24d2fe44d8)) 100 | * specify type for inline function ([694f21b](https://github.com/nuxt-community/composition-api/commit/694f21bbfea097ea2409f70c8a9a8f700143b97f)) 101 | * write static files synchronously ([eb91a5f](https://github.com/nuxt-community/composition-api/commit/eb91a5f5d85f98b6bd5a425aab28c151183abe0b)) 102 | 103 | ### [0.9.3](https://github.com/nuxt-community/composition-api/compare/0.9.2...0.9.3) (2020-06-13) 104 | 105 | 106 | ### Features 107 | 108 | * add `ssrPromise` functionality ([461939d](https://github.com/nuxt-community/composition-api/commit/461939da38b0386c95b328ae49a10d2e57536aad)), closes [#115](https://github.com/nuxt-community/composition-api/issues/115) 109 | 110 | ### [0.9.2](https://github.com/nuxt-community/composition-api/compare/0.9.1...0.9.2) (2020-06-13) 111 | 112 | 113 | ### Features 114 | 115 | * provide access to global `setup()` function ([7fd70d9](https://github.com/nuxt-community/composition-api/commit/7fd70d9ccb4687a3a20aceeb24a3295d53875146)), closes [#104](https://github.com/nuxt-community/composition-api/issues/104) [#111](https://github.com/nuxt-community/composition-api/issues/111) 116 | 117 | 118 | ### Bug Fixes 119 | 120 | * remove duplicate static path definition ([48b9afb](https://github.com/nuxt-community/composition-api/commit/48b9afbe0531ffa90090c67d69d8fc65f02fa868)) 121 | 122 | 123 | ### Performance Improvements 124 | 125 | * remove unnecessary spread ([2300a93](https://github.com/nuxt-community/composition-api/commit/2300a93d79e826526645d732f57bf9712f59e52e)) 126 | 127 | ### [0.9.1](https://github.com/nuxt-community/composition-api/compare/0.9.0...0.9.1) (2020-06-11) 128 | 129 | ## [0.9.0](https://github.com/nuxt-community/composition-api/compare/0.8.2...0.9.0) (2020-06-10) 130 | 131 | 132 | ### ⚠ BREAKING CHANGES 133 | 134 | * There are a number of breaking changes in the latest upstream composition-api. See https://github.com/vuejs/composition-api/releases/tag/v0.6.0 for details. 135 | 136 | ### Features 137 | 138 | * upgrade to `@vue/composition-api` v0.6.1 ([3d0bdc6](https://github.com/nuxt-community/composition-api/commit/3d0bdc62b509e0e95896ff8da79f12feaf0fb60b)) 139 | 140 | ### [0.8.2](https://github.com/nuxt-community/composition-api/compare/0.8.1...0.8.2) (2020-06-04) 141 | 142 | 143 | ### Bug Fixes 144 | 145 | * regression if `ssrRef` is used outside setup (in dev) ([f2beecd](https://github.com/nuxt-community/composition-api/commit/f2beecd38396a1942a52aceb55cc516eb9de37b7)) 146 | 147 | ### [0.8.1](https://github.com/nuxt-community/composition-api/compare/0.8.0...0.8.1) (2020-06-02) 148 | 149 | 150 | ### Bug Fixes 151 | 152 | * re-run `ssrRef` factory functions on hot module reload ([282b8d2](https://github.com/nuxt-community/composition-api/commit/282b8d273cf049d9015d29ee8dc60f2ff0478248)), closes [#88](https://github.com/nuxt-community/composition-api/issues/88) 153 | 154 | ## [0.8.0](https://github.com/nuxt-community/composition-api/compare/0.7.5...0.8.0) (2020-05-30) 155 | 156 | 157 | ### Features 158 | 159 | * add `useStatic` helper to inline results of API calls ([#79](https://github.com/nuxt-community/composition-api/issues/79)) ([460b615](https://github.com/nuxt-community/composition-api/commit/460b6152c93f678781a79a628a16894ca7795a02)) 160 | 161 | ### [0.7.5](https://github.com/nuxt-community/composition-api/compare/0.7.4...0.7.5) (2020-05-30) 162 | 163 | 164 | ### Bug Fixes 165 | 166 | * allow extension of `useContext` return type ([24d7586](https://github.com/nuxt-community/composition-api/commit/24d75865be7d6226aafd871cae7f2346bef021e1)), closes [#84](https://github.com/nuxt-community/composition-api/issues/84) 167 | 168 | ### [0.7.4](https://github.com/nuxt-community/composition-api/compare/0.7.3...0.7.4) (2020-05-28) 169 | 170 | 171 | ### Bug Fixes 172 | 173 | * transpile buildModule ([2d5388d](https://github.com/nuxt-community/composition-api/commit/2d5388de3433d157a71393cc121fd34ed1398dad)), closes [#80](https://github.com/nuxt-community/composition-api/issues/80) 174 | 175 | ### [0.7.3](https://github.com/nuxt-community/composition-api/compare/0.7.2...0.7.3) (2020-05-27) 176 | 177 | 178 | ### Bug Fixes 179 | 180 | * `useMeta` refs should be reactive ([#81](https://github.com/nuxt-community/composition-api/issues/81)) ([74cb5ef](https://github.com/nuxt-community/composition-api/commit/74cb5ef4b5b1e896bd64af3b9fb43ddb636f5894)) 181 | 182 | ### [0.7.2](https://github.com/nuxt-community/composition-api/compare/0.7.1...0.7.2) (2020-05-27) 183 | 184 | 185 | ### Bug Fixes 186 | 187 | * allow custom `globalContext` and `globalNuxt` ([#80](https://github.com/nuxt-community/composition-api/issues/80)) ([8b8d7eb](https://github.com/nuxt-community/composition-api/commit/8b8d7eb73a70fedfff630709fc03f6958a8d4961)) 188 | * reduce chance of `$fetch`/`$fetchState` collision ([534dc43](https://github.com/nuxt-community/composition-api/commit/534dc432af43934d079b1cb0a4c8231e27053f6e)), closes [#74](https://github.com/nuxt-community/composition-api/issues/74) 189 | * sanitise ssrRefs ([fcb4a9d](https://github.com/nuxt-community/composition-api/commit/fcb4a9daca955b9537c6996f1de798b7eae6f94a)) 190 | 191 | ### [0.7.1](https://github.com/nuxt-community/composition-api/compare/0.7.0...0.7.1) (2020-05-19) 192 | 193 | 194 | ### Bug Fixes 195 | 196 | * register composition api before layouts ([93024a8](https://github.com/nuxt-community/composition-api/commit/93024a827af853c061ff70429a835ebc7df52786)), closes [#64](https://github.com/nuxt-community/composition-api/issues/64) [#65](https://github.com/nuxt-community/composition-api/issues/65) 197 | 198 | ## [0.7.0](https://github.com/nuxt-community/composition-api/compare/0.6.3...0.7.0) (2020-05-14) 199 | 200 | 201 | ### ⚠ BREAKING CHANGES 202 | 203 | * `route`, `query`, `from` and `params` are now returned as refs from `useContext`, which was probably what you wanted anyway. 204 | 205 | ### Bug Fixes 206 | 207 | * make route-related context reactive ([91292c8](https://github.com/nuxt-community/composition-api/commit/91292c8cb2d3cc2954ca67514ac59ab006f3ae73)) 208 | 209 | ### [0.6.3](https://github.com/nuxt-community/composition-api/compare/0.6.2...0.6.3) (2020-05-11) 210 | 211 | 212 | ### Features 213 | 214 | * create shallowSsrRef ([#49](https://github.com/nuxt-community/composition-api/issues/49)) ([5ef0f6c](https://github.com/nuxt-community/composition-api/commit/5ef0f6c8d6597e38b7396fdad9ef087eb4194eb6)) 215 | 216 | ### [0.6.2](https://github.com/nuxt-community/composition-api/compare/0.6.1...0.6.2) (2020-05-11) 217 | 218 | 219 | ### Features 220 | 221 | * use appropriate core-js polyfill for ie11 ([5281e66](https://github.com/nuxt-community/composition-api/commit/5281e66b1e77d762056909a2157ddbc72253fc71)) 222 | 223 | 224 | ### Bug Fixes 225 | 226 | * insert composition api plugin before others ([2ef608b](https://github.com/nuxt-community/composition-api/commit/2ef608b456fe945b47661d82af337dcd20c390c5)) 227 | * reduce meta type definitions ([fa9efa3](https://github.com/nuxt-community/composition-api/commit/fa9efa37c8eadd71c523a7cdbe0e574e210f4f8b)) 228 | * revert data sanitisation ([1a4bbed](https://github.com/nuxt-community/composition-api/commit/1a4bbed5025b9f1deb8294d568cf419d27b99a5e)) 229 | * sanitise `ssrRef` data ([d86fdb2](https://github.com/nuxt-community/composition-api/commit/d86fdb2ec6b00f437fe2c1786133b18fffe78a3b)) 230 | 231 | ### [0.6.1](https://github.com/nuxt-community/composition-api/compare/0.6.0...0.6.1) (2020-05-10) 232 | 233 | 234 | ### Features 235 | 236 | * allow `ssrRef` to work outside of setup ([#46](https://github.com/nuxt-community/composition-api/issues/46)) ([6a67c05](https://github.com/nuxt-community/composition-api/commit/6a67c05537f87254d2dcb0c95dd43c4747431a5f)) 237 | 238 | 239 | ### Bug Fixes 240 | 241 | * improve default values for `useMeta` ([f3024e1](https://github.com/nuxt-community/composition-api/commit/f3024e1e79a01a2d9714b4549bd6d6966b2ef260)) 242 | 243 | ## [0.6.0](https://github.com/nuxt-community/composition-api/compare/0.5.0...0.6.0) (2020-05-09) 244 | 245 | 246 | ### ⚠ BREAKING CHANGES 247 | 248 | * `useHead()` no longer exists and instead `useMeta` can be used directly within `setup`, as long as a minimal `head: {}` is defined in the component options. 249 | 250 | _Note_: Work has already begun on composable hooks for `vue-meta` that will mean we don't need to define a minimal `head` object: see [here](5d0eb1ab60ce476ed8a97e97d4d409e74284df9b). 251 | 252 | ### Features 253 | 254 | * allow automatic injection of `head()` into components ([#43](https://github.com/nuxt-community/composition-api/issues/43)) ([f1bda39](https://github.com/nuxt-community/composition-api/commit/f1bda396ea096e42dff645df3275b5d4d288ac73)), closes [#41](https://github.com/nuxt-community/composition-api/issues/41) 255 | * return `$fetch` and `$fetchState` from `useFetch` ([c45177f](https://github.com/nuxt-community/composition-api/commit/c45177f341c57fd40136aaac8e0c55e97c4edd4a)) 256 | 257 | ## [0.5.0](https://github.com/nuxt-community/composition-api/compare/0.4.1...0.5.0) (2020-05-08) 258 | 259 | 260 | ### Features 261 | 262 | * add `useAsync` and improve `ssrRef` ([#28](https://github.com/nuxt-community/composition-api/issues/28)) ([31c9729](https://github.com/nuxt-community/composition-api/commit/31c9729885e290415dddc3e33a36b4912c29feb8)) 263 | 264 | 265 | ### Bug Fixes 266 | 267 | * address cross-request state pollution ([b7248c7](https://github.com/nuxt-community/composition-api/commit/b7248c7bf87a7815180629247ccc65f235746565)) 268 | * remove no-setup `ssrRefs` until solution ([707fb25](https://github.com/nuxt-community/composition-api/commit/707fb25ee4243d28b4a43eb8e41b4eac37134492)) 269 | 270 | 271 | ### Performance Improvements 272 | 273 | * improve memory usage ([e3f7221](https://github.com/nuxt-community/composition-api/commit/e3f722187793dfab1dcb0d99e70261b574ceb97c)) 274 | 275 | ### [0.4.1](https://github.com/nuxt-community/composition-api/compare/0.4.0...0.4.1) (2020-05-08) 276 | 277 | 278 | ### Bug Fixes 279 | 280 | * hotfix so ssrRefs work in production ([7c15c92](https://github.com/nuxt-community/composition-api/commit/7c15c928530f417b3c0ff25c2b0c1f852ac017a7)), closes [#28](https://github.com/nuxt-community/composition-api/issues/28) 281 | 282 | ## [0.4.0](https://github.com/nuxt-community/composition-api/compare/0.3.2...0.4.0) (2020-05-08) 283 | 284 | 285 | ### ⚠ BREAKING CHANGES 286 | 287 | * `withContext` is now deprecated 288 | 289 | ### Features 290 | 291 | * add `useContext` helper function ([9752a61](https://github.com/nuxt-community/composition-api/commit/9752a6124fa545f172bbfbe27dc1e6ef849510a7)), closes [#29](https://github.com/nuxt-community/composition-api/issues/29) 292 | * add composition api helper to interact with `head()` ([#35](https://github.com/nuxt-community/composition-api/issues/35)) ([b7467e2](https://github.com/nuxt-community/composition-api/commit/b7467e2075b61ce7bd23a66d96f9c4e8d124e4f5)) 293 | 294 | 295 | ### Bug Fixes 296 | 297 | * correctly type `ssrRef` with factory function ([7b734ac](https://github.com/nuxt-community/composition-api/commit/7b734ac038cde3fbf967b89a39e2f57844f046e1)) 298 | * make `useContext` API available ([#39](https://github.com/nuxt-community/composition-api/issues/39)) ([dc4f028](https://github.com/nuxt-community/composition-api/commit/dc4f028a2adcc5ee9d663ee9fe2217dd891a7fdf)) 299 | 300 | ### [0.3.2](https://github.com/nuxt-community/composition-api/compare/0.3.1...0.3.2) (2020-05-04) 301 | 302 | 303 | ### Bug Fixes 304 | 305 | * purge non-stringifiable values from ssrRefs ([ac199b1](https://github.com/nuxt-community/composition-api/commit/ac199b18b722774ef1b50936565cafcbc8689e1a)) 306 | 307 | ### [0.3.1](https://github.com/nuxt-community/composition-api/compare/0.3.0...0.3.1) (2020-05-04) 308 | 309 | 310 | ### Bug Fixes 311 | 312 | * set `ssrContext` through plugin ([3ba85f5](https://github.com/nuxt-community/composition-api/commit/3ba85f5fc65dcc9e1e121db2a72fbd13e0cd6565)) 313 | * use own `onServerPrefetch` for `useFetch` ([60e23dd](https://github.com/nuxt-community/composition-api/commit/60e23dd4930156ff9b3c3025478aa53b02003a86)) 314 | 315 | ## [0.3.0](https://github.com/nuxt-community/composition-api/compare/0.2.3...0.3.0) (2020-05-04) 316 | 317 | 318 | ### Features 319 | 320 | * add `ssrRef` capability for automatic SSR support ([#23](https://github.com/nuxt-community/composition-api/issues/23)) ([f27fae8](https://github.com/nuxt-community/composition-api/commit/f27fae886836428410e607ce77d9c066b6596f22)) 321 | 322 | 323 | ### Reverts 324 | 325 | * Revert "docs: remove live demo link (Now is rate-limiting deploy)" ([bbd8661](https://github.com/nuxt-community/composition-api/commit/bbd86618310d897ca041015c0a39248a6ddaef69)) 326 | 327 | ### [0.2.3](https://github.com/danielroe/nuxt-composition-api/compare/0.2.2...0.2.3) (2020-05-01) 328 | 329 | 330 | ### Bug Fixes 331 | 332 | * respect `fetchOnServer` option ([368f33d](https://github.com/danielroe/nuxt-composition-api/commit/368f33d08a0391392bad50b075282902af0ee4cb)) 333 | 334 | ### [0.2.2](https://github.com/danielroe/nuxt-composition-api/compare/0.2.1...0.2.2) (2020-04-30) 335 | 336 | 337 | ### Bug Fixes 338 | 339 | * correctly match `$fetch` and `$fetchState` features ([e2d0442](https://github.com/danielroe/nuxt-composition-api/commit/e2d0442190055608e56eab83316acc08dfb17c4b)) 340 | 341 | ### [0.2.1](https://github.com/danielroe/nuxt-composition-api/compare/0.2.0...0.2.1) (2020-04-30) 342 | 343 | 344 | ### Bug Fixes 345 | 346 | * require `@vue/composition-api` ([f81182e](https://github.com/danielroe/nuxt-composition-api/commit/f81182e6cdc0b03b5f1f72885cc022aa20f01b36)) 347 | 348 | ## [0.2.0](https://github.com/danielroe/nuxt-composition-api/compare/0.1.5...0.2.0) (2020-04-30) 349 | 350 | 351 | ### Features 352 | 353 | * enable this to be used as a nuxt module ([#13](https://github.com/danielroe/nuxt-composition-api/issues/13)) ([9c5dee7](https://github.com/danielroe/nuxt-composition-api/commit/9c5dee79b10a4b6699c8bbaf54d4a12317f2a08a)) 354 | 355 | ### [0.1.5](https://github.com/danielroe/nuxt-composition-api/compare/0.1.4...0.1.5) (2020-04-29) 356 | 357 | 358 | ### Bug Fixes 359 | 360 | * disable browser build ([849edee](https://github.com/danielroe/nuxt-composition-api/commit/849edee610ee536e2755999f878e669019deb363)) 361 | 362 | ### [0.1.4](https://github.com/danielroe/nuxt-composition-api/compare/0.1.3...0.1.4) (2020-04-29) 363 | 364 | ### [0.1.3](https://github.com/danielroe/nuxt-composition-api/compare/0.1.2...0.1.3) (2020-04-28) 365 | 366 | 367 | ### Bug Fixes 368 | 369 | * set $fetchState if nonexistent ([d94c40e](https://github.com/danielroe/nuxt-composition-api/commit/d94c40eb8b581bd9f1ab888310985966c7126643)) 370 | 371 | ### [0.1.2](https://github.com/danielroe/nuxt-composition-api/compare/0.1.1...0.1.2) (2020-04-27) 372 | 373 | 374 | ### ⚠ BREAKING CHANGES 375 | 376 | * `onFetch` can now be accessed using `useFetch` 377 | 378 | ### Code Refactoring 379 | 380 | * rename onFetch to useFetch ([3647769](https://github.com/danielroe/nuxt-composition-api/commit/3647769b8db96f8dcc0463ea4a820eb712ef97ca)) 381 | 382 | ### 0.1.1 (2020-04-27) 383 | 384 | 385 | ### Features 386 | 387 | * add withContext hook ([179f0e1](https://github.com/danielroe/nuxt-composition-api/commit/179f0e1ab7b0d67499c1814c0101fd7037b66490)) 388 | 389 | --------------------------------------------------------------------------------