├── docs ├── .gitignore ├── static │ └── .gitignore ├── nuxt.config.js ├── pages │ └── en │ │ ├── 7.examples │ │ └── useFetch.md │ │ ├── 4.lifecycle │ │ ├── 1.onGlobalSetup.md │ │ └── 2.useFetch.md │ │ ├── index.md │ │ ├── 6.API │ │ ├── 2.useContext.md │ │ ├── 4.wrap.md │ │ ├── 1.useAsync.md │ │ └── 3.useStatic.md │ │ ├── 3.typings │ │ └── 1.definitionHelpers.md │ │ ├── 5.data │ │ ├── 1.reqRef.md │ │ ├── 2.ssrPromise.md │ │ └── 3.ssrRef.md │ │ ├── 2.packages │ │ ├── 2.store.md │ │ ├── 1.routes.md │ │ └── 3.useMeta.md │ │ └── 1.getting-started │ │ ├── 2.setup.md │ │ ├── 1.introduction.md │ │ └── 3.gotchas.md ├── package.json └── docus.config.ts ├── .nowignore ├── module.js ├── src ├── runtime │ ├── index.ts │ ├── templates │ │ ├── globals.mjs │ │ ├── meta.mjs │ │ ├── plugin.mjs │ │ └── polyfill.client.mjs │ ├── composables │ │ ├── defineHelpers.ts │ │ ├── utils.ts │ │ ├── index.ts │ │ ├── component.ts │ │ ├── req-ref.ts │ │ ├── hooks.ts │ │ ├── async.ts │ │ ├── context.ts │ │ ├── wrappers.ts │ │ ├── vue.ts │ │ ├── meta.ts │ │ ├── static.ts │ │ ├── ssr-ref.ts │ │ └── fetch.ts │ └── globals.ts ├── babel-plugin │ ├── index.js │ └── index.ts └── module │ ├── static.ts │ ├── globals-register.ts │ ├── babel-register.ts │ ├── utils.ts │ ├── vite-plugin.ts │ └── index.ts ├── test ├── fixture │ ├── api │ │ ├── package.json │ │ └── posts.js │ ├── store │ │ └── index.js │ ├── pages │ │ ├── other.vue │ │ ├── wrappers.vue │ │ ├── ttfb.vue │ │ ├── promise.vue │ │ ├── req-ref.vue │ │ ├── no-setup.vue │ │ ├── static │ │ │ └── _id.vue │ │ ├── hooks.vue │ │ ├── route │ │ │ └── _slug.vue │ │ ├── meta.vue │ │ ├── ssr-ref.vue │ │ └── index.vue │ ├── state │ │ └── index.js │ ├── utils │ │ └── index.js │ ├── layouts │ │ └── default.vue │ ├── components │ │ └── comp.vue │ ├── plugins │ │ └── global.js │ └── nuxt.config.ts ├── 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 │ ├── fetch.test-d.ts │ ├── createHelpers.test-d.ts │ └── ssr-ref.test-d.ts └── e2e │ ├── promise.ts │ ├── wrappers.ts │ ├── req-refs.ts │ ├── hooks.ts │ ├── route.ts │ ├── ssr-refs.ts │ ├── helpers │ └── index.ts │ ├── static.ts │ ├── fetch.ts │ └── meta.ts ├── .prettierignore ├── lint-staged.config.js ├── .eslintignore ├── prettier.config.js ├── .gitignore ├── .babelrc.js ├── example ├── plugins │ └── vue-placeholders.js ├── types │ └── vue.d.ts ├── README.md ├── tsconfig.json ├── package.json ├── layouts │ └── default.vue ├── components │ └── Author.vue ├── nuxt.config.js └── pages │ ├── index.vue │ └── posts │ └── _id.vue ├── siroc.config.js ├── renovate.json ├── vitest.config.ts ├── .release-it.json ├── netlify.toml ├── .eslintrc.js ├── .github ├── ISSUE_TEMPLATE │ ├── ---help-wanted.md │ ├── ---documentation.md │ ├── ---feature-suggestion.md │ └── ---bug-report.md └── workflows │ ├── codeql-analysis.yml │ └── ci.yml ├── vercel.json ├── tsconfig.json ├── LICENCE ├── README.md ├── CODE_OF_CONDUCT.md ├── package.json └── CHANGELOG.md /docs/.gitignore: -------------------------------------------------------------------------------- 1 | static/sw.js -------------------------------------------------------------------------------- /docs/static/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /.nowignore: -------------------------------------------------------------------------------- 1 | lib 2 | dist 3 | .github 4 | coverage 5 | 6 | -------------------------------------------------------------------------------- /module.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./dist/module') 2 | -------------------------------------------------------------------------------- /src/runtime/index.ts: -------------------------------------------------------------------------------- 1 | export * from './composables' 2 | -------------------------------------------------------------------------------- /src/runtime/templates/globals.mjs: -------------------------------------------------------------------------------- 1 | <%= options.contents %> -------------------------------------------------------------------------------- /test/fixture/api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api" 3 | } 4 | -------------------------------------------------------------------------------- /test/fixture/store/index.js: -------------------------------------------------------------------------------- 1 | export const state = () => ({}) 2 | -------------------------------------------------------------------------------- /test/fixture/pages/other.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | lib 2 | example 3 | .nuxt 4 | ./dist 5 | coverage 6 | templates 7 | CHANGELOG.md -------------------------------------------------------------------------------- /lint-staged.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | '*.{js,ts,json}': ['yarn lint:eslint'], 3 | } 4 | -------------------------------------------------------------------------------- /src/babel-plugin/index.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line 2 | module.exports = require('jiti')(__dirname)('./index.ts') 3 | -------------------------------------------------------------------------------- /src/runtime/templates/meta.mjs: -------------------------------------------------------------------------------- 1 | import { setMetaPlugin } from '@nuxtjs/composition-api' 2 | 3 | export default setMetaPlugin 4 | -------------------------------------------------------------------------------- /src/runtime/templates/plugin.mjs: -------------------------------------------------------------------------------- 1 | import { globalPlugin } from '@nuxtjs/composition-api' 2 | 3 | export default globalPlugin 4 | -------------------------------------------------------------------------------- /test/unit/setup/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | Vue.config.productionTip = false 4 | Vue.config.devtools = false 5 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | lib 2 | test/fixture 3 | test/e2e 4 | example 5 | .nuxt 6 | ./dist 7 | docs 8 | coverage 9 | templates 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 | -------------------------------------------------------------------------------- /docs/nuxt.config.js: -------------------------------------------------------------------------------- 1 | import { withDocus } from 'docus' 2 | 3 | // Learn more at https://docus.dev 4 | export default withDocus({ 5 | rootDir: __dirname, 6 | }) 7 | -------------------------------------------------------------------------------- /example/plugins/vue-placeholders.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | // @ts-ignore 3 | import VueContentPlaceholders from 'vue-content-placeholders' 4 | 5 | Vue.use(VueContentPlaceholders) 6 | -------------------------------------------------------------------------------- /example/types/vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import { DefineComponent } from 'vue' 3 | const component: DefineComponent<{}, {}, any> 4 | export default component 5 | } 6 | -------------------------------------------------------------------------------- /siroc.config.js: -------------------------------------------------------------------------------- 1 | import { defineSirocConfig } from 'siroc' 2 | 3 | export default defineSirocConfig({ 4 | rollup: { 5 | externals: ['@nuxtjs/composition-api/dist/runtime/globals'], 6 | }, 7 | }) 8 | -------------------------------------------------------------------------------- /docs/pages/en/7.examples/useFetch.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useFetch 3 | category: Examples 4 | --- 5 | 6 | :::code-sandbox{src="https://codesandbox.io/s/github/nuxt-community/composition-api/tree/main/example?from-embed"} 7 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@nuxtjs"], 3 | "ignoreDeps": ["testcafe", "docus"], 4 | "packageRules": [ 5 | { 6 | "matchPackageNames": ["node"], 7 | "enabled": false 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /src/runtime/templates/polyfill.client.mjs: -------------------------------------------------------------------------------- 1 | // Necessary polyfill for Composition API support for IE11 2 | import '<%= options.corejsPolyfill === "3" ? "core-js/features/reflect/own-keys" : "core-js/modules/es6.reflect.own-keys" %>' 3 | -------------------------------------------------------------------------------- /src/runtime/composables/defineHelpers.ts: -------------------------------------------------------------------------------- 1 | import { Middleware, Plugin } from '@nuxt/types' 2 | 3 | export const defineNuxtPlugin = (plugin: Plugin) => plugin 4 | export const defineNuxtMiddleware = (middleware: Middleware) => middleware 5 | -------------------------------------------------------------------------------- /test/fixture/state/index.js: -------------------------------------------------------------------------------- 1 | import { reqRef, ssrRef, reqSsrRef } from '@nuxtjs/composition-api' 2 | 3 | export const reqRefCounter = reqRef(0) 4 | export const ssrRefCounter = ssrRef(0) 5 | export const reqSsrRefCounter = reqSsrRef(0) 6 | -------------------------------------------------------------------------------- /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 | "docus": "^0.5.6" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/tsd/component.test-d.ts: -------------------------------------------------------------------------------- 1 | import { expectType } from 'tsd' 2 | import { defineComponent } from 'vue' 3 | 4 | import { defineComponent as modifiedDefineComponent } from '../..' 5 | 6 | expectType(modifiedDefineComponent) 7 | -------------------------------------------------------------------------------- /test/fixture/api/posts.js: -------------------------------------------------------------------------------- 1 | /** @type {import('@nuxt/types').ServerMiddleware} */ 2 | module.exports = (req, res) => { 3 | res.setHeader('Content-Type', 'application/json') 4 | res.end(JSON.stringify({ id: req.url?.split('/').slice(-1)[0] })) 5 | } 6 | -------------------------------------------------------------------------------- /docs/docus.config.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | title: 'Nuxt Composition API', 3 | url: 'https://composition-api.nuxtjs.org', 4 | twitter: 'nuxt_js', 5 | github: { 6 | repo: 'nuxt-community/composition-api', 7 | branch: 'main', 8 | }, 9 | } 10 | -------------------------------------------------------------------------------- /test/fixture/utils/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} result 3 | * @param {number} [time=100] 4 | */ 5 | export function fetcher(result, time = 100) { 6 | return new Promise(resolve => { 7 | return setTimeout(() => { 8 | resolve(result) 9 | }, time) 10 | }) 11 | } 12 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath } from 'url' 2 | import { defineConfig } from 'vitest/config' 3 | 4 | export default defineConfig({ 5 | resolve: { 6 | alias: { 7 | '@nuxtjs/composition-api': fileURLToPath(new URL('.', import.meta.url)), 8 | }, 9 | }, 10 | }) 11 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/runtime/globals.ts: -------------------------------------------------------------------------------- 1 | // This is a shim for runtime/templates/globals.mjs 2 | 3 | export const globalNuxt = '$nuxt' 4 | 5 | export const globalContext = '__NUXT__' 6 | 7 | export const isFullStatic = false 8 | 9 | export const staticPath = '/static-path' 10 | 11 | export const publicPath = '/_nuxt/' 12 | -------------------------------------------------------------------------------- /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 | }, 9 | "npm": { 10 | "skipChecks": true 11 | }, 12 | "plugins": { 13 | "@release-it/conventional-changelog": { 14 | "preset": "conventionalcommits", 15 | "infile": "CHANGELOG.md" 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /test/tsd/fetch.test-d.ts: -------------------------------------------------------------------------------- 1 | import { expectType } from 'tsd' 2 | 3 | import { useFetch } from '../..' 4 | 5 | expectType<{ 6 | fetch: () => void | Promise 7 | $fetch: () => void | Promise 8 | fetchState: { 9 | error: Error | null 10 | pending: boolean 11 | timestamp: number 12 | } 13 | $fetchState: { 14 | error: Error | null 15 | pending: boolean 16 | timestamp: number 17 | } 18 | }>( 19 | useFetch(async () => { 20 | // 21 | }) 22 | ) 23 | -------------------------------------------------------------------------------- /src/runtime/composables/utils.ts: -------------------------------------------------------------------------------- 1 | import { getCurrentInstance as getVM } from 'vue' 2 | 3 | export function validateKey(key?: T): asserts key is T { 4 | if (!key) { 5 | throw new Error( 6 | "You must provide a key. You can have it generated automatically by adding '@nuxtjs/composition-api/dist/babel-plugin' to your Babel plugins." 7 | ) 8 | } 9 | } 10 | 11 | export const getCurrentInstance = () => { 12 | const vm = getVM() 13 | 14 | if (!vm) return 15 | 16 | return vm.proxy 17 | } 18 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | browser: true, 5 | es6: true, 6 | node: true, 7 | }, 8 | rules: { 9 | '@typescript-eslint/no-explicit-any': 'off', 10 | '@typescript-eslint/no-inferrable-types': 1, 11 | '@typescript-eslint/explicit-module-boundary-types': 'off', 12 | '@typescript-eslint/explicit-function-return-type': 'off', 13 | }, 14 | extends: [ 15 | 'plugin:promise/recommended', 16 | 'plugin:@typescript-eslint/recommended', 17 | ], 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 | -------------------------------------------------------------------------------- /test/tsd/createHelpers.test-d.ts: -------------------------------------------------------------------------------- 1 | import { 2 | defineNuxtPlugin, 3 | defineNuxtMiddleware, 4 | } from '../../src/runtime/composables/defineHelpers' 5 | import { expectType } from 'tsd' 6 | 7 | defineNuxtPlugin((context, inject) => { 8 | const hello = (msg: string) => console.log(`Hello ${msg}!`) 9 | 10 | expectType(context.isDev) 11 | 12 | inject('hello', hello) 13 | }) 14 | 15 | defineNuxtMiddleware(({ store, redirect }) => { 16 | if (!store.state.authenticated) { 17 | return redirect('/login') 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 | -------------------------------------------------------------------------------- /docs/pages/en/4.lifecycle/1.onGlobalSetup.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: onGlobalSetup 3 | description: 'You can run functions (or provide data) in the global Nuxt setup() function.' 4 | --- 5 | 6 | This helper will run a callback function in the global setup function. 7 | 8 | ```ts[~/plugins/myPlugin.js] 9 | import { onGlobalSetup, provide } from '@nuxtjs/composition-api' 10 | 11 | export default () => { 12 | onGlobalSetup(() => { 13 | provide('globalKey', true) 14 | }) 15 | } 16 | ``` 17 | 18 | :::alert 19 | This should be called from within a plugin rather than in a component context. 20 | ::: 21 | -------------------------------------------------------------------------------- /.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 | **📚 Is your documentation request related to a problem? Please describe.** 10 | 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. 11 | 12 | **🔍 Where should you find it?** 13 | What page of the docs do you expect this information to be found on? 14 | 15 | **ℹ️ Additional context** 16 | Add any other context or information. 17 | -------------------------------------------------------------------------------- /example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "rootDir": ".", 5 | "strict": true, 6 | "sourceMap": true, 7 | "allowJs": true, 8 | "checkJs": true, 9 | "noEmit": true, 10 | "target": "ESNext", 11 | "module": "CommonJS", 12 | "moduleResolution": "Node", 13 | "lib": [ 14 | "ESNext", 15 | "DOM" 16 | ], 17 | "esModuleInterop": true, 18 | "resolveJsonModule": true, 19 | "allowSyntheticDefaultImports": true, 20 | "types": [ 21 | "node", 22 | "@nuxt/http", 23 | "@nuxt/types", 24 | ] 25 | }, 26 | } 27 | -------------------------------------------------------------------------------- /test/fixture/layouts/default.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 29 | -------------------------------------------------------------------------------- /src/runtime/composables/index.ts: -------------------------------------------------------------------------------- 1 | export { useAsync } from './async' 2 | export { defineComponent } from './component' 3 | export { useContext, withContext } from './context' 4 | export * from './defineHelpers' 5 | export { useFetch } from './fetch' 6 | export { globalPlugin, onGlobalSetup, setMetaPlugin } from './hooks' 7 | export { useMeta } from './meta' 8 | export { reqRef, reqSsrRef } from './req-ref' 9 | export { ssrRef, shallowSsrRef, setSSRContext, ssrPromise } from './ssr-ref' 10 | export { useStatic } from './static' 11 | export { useRoute, useRouter, useStore, wrapProperty } from './wrappers' 12 | 13 | export * from './vue' 14 | -------------------------------------------------------------------------------- /test/fixture/pages/wrappers.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 29 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/---feature-suggestion.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F195 Feature suggestion" 3 | about: Suggest an idea 4 | title: 'feat: ' 5 | labels: enhancement 6 | assignees: danielroe 7 | --- 8 | 9 | **🆒 Your use case** 10 | Add a description of your use case, and how this feature would help you. 11 | 12 | > Ex. When I do [...] I would expect to be able to do [...] 13 | 14 | **🆕 The solution you'd like** 15 | Describe what you want to happen. 16 | 17 | **🔍 Alternatives you've considered** 18 | Have you considered any alternative solutions or features? 19 | 20 | **ℹ️ Additional info** 21 | Is there any other context you think would be helpful to know? 22 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-hello-world", 3 | "dependencies": { 4 | "@nuxt/http": "^0.6.4", 5 | "@nuxtjs/composition-api": "latest", 6 | "vue-content-placeholders": "^0.2.1" 7 | }, 8 | "devDependencies": { 9 | "@nuxt/types": "^2.18.1", 10 | "@types/node-fetch": "^2.6.12", 11 | "nuxt": "^2.18.1" 12 | }, 13 | "scripts": { 14 | "dev": "nuxt", 15 | "build": "nuxt build", 16 | "now-build": "NOW_BUILD=true nuxt generate", 17 | "start": "nuxt start", 18 | "generate": "nuxt generate", 19 | "post-update": "yarn upgrade --latest" 20 | }, 21 | "volta": { 22 | "node": "16.16.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test/e2e/promise.ts: -------------------------------------------------------------------------------- 1 | import { Selector } from 'testcafe' 2 | import { navigateTo, expectOnPage } from './helpers' 3 | 4 | // eslint-disable-next-line 5 | fixture`ssrPromise` 6 | 7 | test('Shows data on ssr-loaded page', async t => { 8 | await navigateTo('/promise') 9 | await expectOnPage('promise-server') 10 | 11 | await t.click(Selector('a').withText('home')) 12 | await t.click(Selector('a').withText('promise')) 13 | await expectOnPage('promise-server') 14 | }) 15 | 16 | test('Shows appropriate data on client-loaded page', async t => { 17 | await navigateTo('/') 18 | await t.click(Selector('a').withText('promise')) 19 | await expectOnPage('promise-client') 20 | }) 21 | -------------------------------------------------------------------------------- /example/layouts/default.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 21 | 22 | 35 | -------------------------------------------------------------------------------- /src/runtime/composables/component.ts: -------------------------------------------------------------------------------- 1 | import { defineComponent as define } from 'vue' 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 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "builds": [ 3 | { 4 | "src": "docs/package.json", 5 | "use": "@vercel/static-build" 6 | }, 7 | { 8 | "src": "test/fixture/api/posts.js", 9 | "use": "@vercel/node" 10 | }, 11 | { 12 | "src": "package.json", 13 | "use": "@vercel/static-build" 14 | }, 15 | { 16 | "src": "example/package.json", 17 | "use": "@vercel/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 | -------------------------------------------------------------------------------- /test/unit/__snapshots__/babel-ssr-ref.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 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/fixture/pages/ttfb.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 33 | -------------------------------------------------------------------------------- /test/fixture/pages/promise.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 36 | -------------------------------------------------------------------------------- /test/e2e/wrappers.ts: -------------------------------------------------------------------------------- 1 | import { Selector } from 'testcafe' 2 | import { navigateTo, expectOnPage, expectPathnameToBe } from './helpers' 3 | 4 | // eslint-disable-next-line 5 | fixture`Wrappers` 6 | 7 | test('Shows data on ssr-loaded page', async () => { 8 | await navigateTo('/wrappers') 9 | await expectOnPage('store: true') 10 | await expectOnPage('route: true') 11 | await expectOnPage('router: true') 12 | }) 13 | 14 | test('Shows appropriate data on client-loaded page', async t => { 15 | await navigateTo('/') 16 | await t.click(Selector('a').withText('wrappers')) 17 | await expectPathnameToBe('/wrappers') 18 | await expectOnPage('store: true') 19 | await expectOnPage('route: true') 20 | await expectOnPage('router: true') 21 | }) 22 | -------------------------------------------------------------------------------- /test/fixture/components/comp.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 36 | -------------------------------------------------------------------------------- /test/fixture/pages/req-ref.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 28 | -------------------------------------------------------------------------------- /.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. You can use [this Stackblitz](https://stackblitz.com/edit/github-jc3ome) as a starter. 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/fixture/plugins/global.js: -------------------------------------------------------------------------------- 1 | import { 2 | onGlobalSetup, 3 | useMeta, 4 | provide, 5 | ref, 6 | ssrRef, 7 | useContext, 8 | } from '@nuxtjs/composition-api' 9 | 10 | export const ranSsr = ssrRef(0) 11 | export const ran = ref(0) 12 | 13 | export default () => { 14 | ran.value = 0 15 | if (process.server) ranSsr.value = 0 16 | 17 | onGlobalSetup(() => { 18 | const { $config } = useContext() 19 | const a = ssrRef('test') 20 | a.value = 'another' 21 | 22 | const { title } = useMeta() 23 | title.value = 'My fixture' 24 | 25 | ran.value++ 26 | if (process.server) ranSsr.value++ 27 | 28 | provide('globalKey', true) 29 | provide('globalContext', $config) 30 | 31 | return { 32 | ran, 33 | ranSsr, 34 | } 35 | }) 36 | } 37 | -------------------------------------------------------------------------------- /test/e2e/req-refs.ts: -------------------------------------------------------------------------------- 1 | import { Selector } from 'testcafe' 2 | import { navigateTo, expectOnPage, expectPathnameToBe } from './helpers' 3 | 4 | // eslint-disable-next-line 5 | fixture`reqRef` 6 | 7 | test('Shows data on ssr-loaded page', async () => { 8 | await navigateTo('/req-ref') 9 | await expectOnPage('reqRefCounter: 1') 10 | await expectOnPage('reqSsrRefCounter: 1') 11 | await navigateTo('/req-ref') 12 | await expectOnPage('reqRefCounter: 1') 13 | await expectOnPage('reqSsrRefCounter: 1') 14 | }) 15 | 16 | test('Shows appropriate data on client-loaded page', async t => { 17 | await navigateTo('/') 18 | await t.click(Selector('a').withText('req refs')) 19 | await expectPathnameToBe('/req-ref') 20 | await expectOnPage('reqRefCounter: 1') 21 | await expectOnPage('reqSsrRefCounter: 0') 22 | }) 23 | -------------------------------------------------------------------------------- /src/runtime/composables/req-ref.ts: -------------------------------------------------------------------------------- 1 | import { ref, Ref } from 'vue' 2 | 3 | import { sanitise, ssrRef } from './ssr-ref' 4 | 5 | export const reqRefs = new Set<() => void>() 6 | 7 | /** 8 | * @deprecated 9 | */ 10 | export const reqRef = (initialValue: T): Ref => { 11 | const _ref = ref(initialValue) 12 | 13 | if (process.server) reqRefs.add(() => (_ref.value = initialValue as any)) 14 | 15 | return _ref as Ref 16 | } 17 | 18 | /** 19 | * @deprecated 20 | */ 21 | export const reqSsrRef = (initialValue: T, key?: string) => { 22 | const _ref = ssrRef(initialValue, key) 23 | 24 | if (process.server) 25 | reqRefs.add(() => { 26 | _ref.value = 27 | initialValue instanceof Function 28 | ? sanitise(initialValue()) 29 | : initialValue 30 | }) 31 | 32 | return _ref 33 | } 34 | -------------------------------------------------------------------------------- /docs/pages/en/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | template: landing 3 | title: Nuxt Composition API 4 | description: >- 5 | Vue Composition API in Nuxt 2 6 | navigation: false 7 | --- 8 | 9 | :::block-hero{yml} 10 | title: title 11 | description: description 12 | cta: 13 | 14 | - Get started 15 | - /getting-started/introduction 16 | secondary: 17 | - Open on GitHub 18 | - https://github.com/nuxt-community/composition-api 19 | snippet: yarn add @nuxtjs/composition-api 20 | ::: 21 | 22 | ::::card-grid{title="What's included?"} 23 | :::card{icon="IconVue" title="Vue Composition API"} 24 | Get all the Composition API features in Nuxt 2. 25 | ::: 26 | 27 | :::card{icon="IconNuxt" title="SSR features"} 28 | Additional helpers to interact with SSR. 29 | ::: 30 | 31 | :::card{icon="IconSun" title="Easy migration"} 32 | Be ready for Nuxt 3. 33 | ::: 34 | :::: 35 | -------------------------------------------------------------------------------- /test/unit/babel-ssr-ref.spec.ts: -------------------------------------------------------------------------------- 1 | import { transform } from '@babel/core' 2 | import { describe, it, expect } from 'vitest' 3 | 4 | import plugin from '../../src/babel-plugin' 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 transformed = transform(example, { plugins: [plugin] }) 23 | expect(transformed).toBeTruthy() 24 | const { code } = transformed! 25 | expect(code).toMatchSnapshot() 26 | }) 27 | }) 28 | -------------------------------------------------------------------------------- /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 1 times on server') 9 | await expectOnPage('global setup was run 1 times on client') 10 | await expectOnPage('globally injected value was received') 11 | await expectOnPage('globally injected config from context was injected') 12 | } 13 | 14 | test('Runs plugin on server side page', async () => { 15 | await navigateTo('/hooks') 16 | await assertions() 17 | 18 | await navigateTo('/hooks') 19 | await assertions() 20 | }) 21 | 22 | test('Runs plugin on client rendered page', async t => { 23 | await navigateTo('/') 24 | await t.click(Selector('a').withText('hooks')) 25 | await assertions() 26 | }) 27 | -------------------------------------------------------------------------------- /test/fixture/pages/no-setup.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 40 | -------------------------------------------------------------------------------- /docs/pages/en/6.API/2.useContext.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useContext 3 | description: 'You can access the Nuxt context within the composition API' 4 | --- 5 | 6 | You can access the Nuxt context more easily using `useContext`, which will return the Nuxt context. 7 | 8 | ```ts 9 | import { defineComponent, useContext } from '@nuxtjs/composition-api' 10 | 11 | export default defineComponent({ 12 | setup() { 13 | const { store } = useContext() 14 | store.dispatch('myAction') 15 | }, 16 | }) 17 | ``` 18 | 19 | :::alert{type="info"} 20 | Note that `route`, `query`, `from` and `params` are reactive refs (accessed with `.value`), but the rest of the context is not. 21 | ::: 22 | 23 | :::alert{type="warning"} 24 | To smooth your upgrade to Nuxt 3, it is recommended not to access `route`, `query`, `from` and `params` from `useContext` but rather to use the `useRoute` helper function. 25 | ::: 26 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "rootDir": ".", 5 | "strict": true, 6 | "sourceMap": true, 7 | "allowJs": true, 8 | "checkJs": true, 9 | "target": "ESNext", 10 | "module": "CommonJS", 11 | "moduleResolution": "Node", 12 | "lib": ["ESNext", "DOM"], 13 | "esModuleInterop": true, 14 | "resolveJsonModule": true, 15 | "allowSyntheticDefaultImports": true, 16 | "paths": { 17 | "@nuxtjs/composition-api/dist/runtime/globals": [ 18 | "./src/runtime/globals.ts" 19 | ], 20 | "@nuxtjs/composition-api": ["./src/runtime/index.ts"] 21 | }, 22 | "types": ["node", "@nuxt/types"] 23 | }, 24 | "include": [ 25 | "src/**/*.ts", 26 | "test/fixture/**/*.vue", 27 | "test/fixture/**/*.js", 28 | "test/fixture/**/*.ts", 29 | "tsd/**/*.ts", 30 | "unit/**/*.ts" 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /test/e2e/route.ts: -------------------------------------------------------------------------------- 1 | import { t, Selector } from 'testcafe' 2 | import { navigateTo, expectOnPage } from './helpers' 3 | 4 | // eslint-disable-next-line 5 | fixture`useRoute` 6 | 7 | async function displaysRouteCorrectly() { 8 | await expectOnPage('path: /route/a') 9 | await expectOnPage('watch function called: 1') 10 | await t.click(Selector('a').withText('Link with query')) 11 | await expectOnPage('route query test: true') 12 | await expectOnPage('watch function called: 2') 13 | await expectOnPage('route param slug: a') 14 | } 15 | 16 | test('Shows correct route info on server-loaded page', async () => { 17 | await navigateTo('/route/a') 18 | await displaysRouteCorrectly() 19 | }) 20 | 21 | test('Shows correct route info on client-loaded page', async t => { 22 | await navigateTo('/') 23 | await t.click(Selector('a').withText('route')) 24 | await displaysRouteCorrectly() 25 | }) 26 | -------------------------------------------------------------------------------- /docs/pages/en/3.typings/1.definitionHelpers.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: defineNuxt* 3 | description: 'You can get automatic type-hinting for Nuxt configuration, plugins, middleware, modules and serverMiddleware.' 4 | --- 5 | 6 | There are some helpers to optimize developer experience when creating Nuxt plugins, middleware, server middleware and modules. 7 | 8 | These helpers simply return the function or object passed into them, adding the correct typings. 9 | 10 | ## defineNuxtPlugin 11 | 12 | Create a plugin with types with: 13 | 14 | ```ts 15 | import { defineNuxtPlugin } from '@nuxtjs/composition-api' 16 | 17 | export default defineNuxtPlugin(ctx => { 18 | // do stuff 19 | }) 20 | ``` 21 | 22 | ## defineNuxtMiddleware 23 | 24 | Create middleware with types with: 25 | 26 | ```ts 27 | import { defineNuxtMiddleware } from '@nuxtjs/composition-api' 28 | 29 | export default defineNuxtMiddleware(ctx => { 30 | // do stuff 31 | }) 32 | ``` 33 | -------------------------------------------------------------------------------- /docs/pages/en/6.API/4.wrap.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: wrapProperty 3 | description: 'You can create a custom helper for any Vue instance property.' 4 | --- 5 | 6 | You might want to create a custom helper to 'convert' a non-Composition API property to a Composition-ready one. `wrapProperty` enables you to do that easily, returning either a computed or a bare property as required. 7 | 8 | (The second argument is a boolean indicating whether the helper function should return a computed property or not, and it defaults to `true`.) 9 | 10 | ```ts 11 | import { defineComponent, wrapProperty } from '@nuxtjs/composition-api' 12 | 13 | // For example, for used with https://github.com/danielroe/typed-vuex 14 | const useAccessor = wrapProperty('$accessor', false) 15 | 16 | export default defineComponent({ 17 | setup() { 18 | const accessor = useAccessor() 19 | // You can now access a fully typed store accessor in your component 20 | }, 21 | }) 22 | ``` 23 | -------------------------------------------------------------------------------- /src/module/static.ts: -------------------------------------------------------------------------------- 1 | import { copyFileSync, existsSync, mkdirpSync, readdirSync } from 'fs-extra' 2 | import { join } from 'pathe' 3 | 4 | import type { NuxtOptions } from '@nuxt/types' 5 | import type { ModuleThis } from '@nuxt/types/config/module' 6 | 7 | export function prepareUseStatic(this: ModuleThis) { 8 | const nuxtOptions: NuxtOptions = this.nuxt.options 9 | 10 | const staticPath = join(nuxtOptions.buildDir, 'static-json') 11 | 12 | this.nuxt.hook('builder:prepared', () => { 13 | mkdirpSync(staticPath) 14 | }) 15 | 16 | this.nuxt.hook('generate:route', () => { 17 | mkdirpSync(staticPath) 18 | }) 19 | 20 | this.nuxt.hook('generate:done', async (generator: any) => { 21 | if (!existsSync(staticPath)) return 22 | 23 | const { distPath } = generator 24 | readdirSync(staticPath).forEach(file => 25 | copyFileSync(join(staticPath, file), join(distPath, file)) 26 | ) 27 | }) 28 | 29 | return { staticPath } 30 | } 31 | -------------------------------------------------------------------------------- /docs/pages/en/5.data/1.reqRef.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: reqRef, reqSsrRef 3 | description: 'You can define server-side refs that are reset on each request.' 4 | --- 5 | 6 | `reqRef` declares a normal `ref` with one key difference. It resets the value of this ref on each request. You can find out [more information here](/getting-started/gotchas#shared-server-state). 7 | 8 | :::alert 9 | You do not need a `reqRef` if you are using an `ssrRef` within a component setup function as it will be automatically tied to the per-request state. 10 | ::: 11 | 12 | :::alert{type="warning"} 13 | You should take especial care because of the danger of shared state when using refs in this way. 14 | ::: 15 | 16 | ## Example 17 | 18 | ```ts[~/state/sampleModule.js] 19 | import { reqRef } from '@nuxtjs/composition-api' 20 | 21 | export const user = reqRef(null) 22 | 23 | export const fetchUser = async () => { 24 | const r = await fetch('https://api.com/users') 25 | user.value = await r.json() 26 | } 27 | ``` 28 | -------------------------------------------------------------------------------- /test/fixture/pages/static/_id.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 40 | -------------------------------------------------------------------------------- /example/components/Author.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 46 | -------------------------------------------------------------------------------- /example/nuxt.config.js: -------------------------------------------------------------------------------- 1 | import fetch from 'node-fetch' 2 | const serverlessEnvironment = !!process.env.NOW_BUILD 3 | 4 | /** @type {import('@nuxt/types').NuxtConfig} */ 5 | export default { 6 | server: { 7 | port: process.env.PORT || 8000, 8 | }, 9 | target: 'static', 10 | plugins: ['@/plugins/vue-placeholders.js'], 11 | modules: ['@nuxt/http'], 12 | ...(serverlessEnvironment ? { 13 | router: { 14 | base: '/example/', 15 | }, 16 | build: { 17 | publicPath: 'example', 18 | }, 19 | } : {}), 20 | buildModules: ['@nuxtjs/composition-api/module'], 21 | generate: { 22 | interval: 2000, 23 | async routes() { 24 | /** @type {{ userId: number, id: number, title: string, body: string }[]} */ 25 | const posts = await fetch('https://jsonplaceholder.typicode.com/posts') 26 | .then(res => res.json()) 27 | .then(d => d.slice(0, 20)) 28 | const routes = posts.map(post => `/posts/${post.id}`) 29 | 30 | return ['/'].concat(routes) 31 | }, 32 | exclude: [RegExp('/posts/23')] 33 | }, 34 | } 35 | -------------------------------------------------------------------------------- /docs/pages/en/2.packages/2.store.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useStore 3 | description: 'Access this.$store with the Nuxt Composition API.' 4 | --- 5 | 6 | Vuex v4 [provides a helper function](https://next.vuex.vuejs.org/api/#usestore) for accessing it within the Composition API. This helper provides an equivalent while using Nuxt 2 7 | 8 | ## useStore 9 | 10 | Returns `this.$store`. 11 | 12 | ```ts 13 | import { defineComponent, useStore } from '@nuxtjs/composition-api' 14 | 15 | export default defineComponent({ 16 | setup() { 17 | const store = useStore() 18 | }, 19 | }) 20 | ``` 21 | 22 | You can also provide an injection key or custom type to get back a semi-typed store: 23 | 24 | ```ts 25 | import { defineComponent, useStore } from '@nuxtjs/composition-api' 26 | 27 | export interface State { 28 | count: number 29 | } 30 | 31 | export const key: InjectionKey> = Symbol() 32 | 33 | export default defineComponent({ 34 | setup() { 35 | const store = useStore(key) 36 | const store = useStore() 37 | // In both of these cases, store.state.count will be typed as a number 38 | }, 39 | }) 40 | ``` 41 | -------------------------------------------------------------------------------- /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/hooks.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 39 | -------------------------------------------------------------------------------- /docs/pages/en/2.packages/1.routes.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useRoute, useRouter 3 | description: 'Access this.$route and this.$router with the Nuxt Composition API.' 4 | --- 5 | 6 | In Vue 3, `vue-router` exports composition functions for accessing the current route and router. 7 | 8 | These helpers provide an equivalent whilst using Nuxt 2. 9 | 10 | ## useRoute 11 | 12 | Returns `this.$route`, wrapped in a computed - so accessible from `.value`. 13 | 14 | ```ts 15 | import { computed, defineComponent, useRoute } from '@nuxtjs/composition-api' 16 | 17 | export default defineComponent({ 18 | setup() { 19 | const route = useRoute() 20 | const id = computed(() => route.value.params.id) 21 | }, 22 | }) 23 | ``` 24 | 25 | :::alert 26 | When migrating to Nuxt 3 you will need to remove `.value` as the native equivalent returns a reactive object, not a computed object. 27 | ::: 28 | 29 | ## useRouter 30 | 31 | Returns `this.$router`. 32 | 33 | ```ts 34 | import { defineComponent, useRouter } from '@nuxtjs/composition-api' 35 | 36 | export default defineComponent({ 37 | setup() { 38 | const router = useRouter() 39 | router.push('/') 40 | }, 41 | }) 42 | ``` 43 | -------------------------------------------------------------------------------- /test/fixture/pages/route/_slug.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 50 | -------------------------------------------------------------------------------- /src/module/globals-register.ts: -------------------------------------------------------------------------------- 1 | import type { ModuleThis } from '@nuxt/types/config/module' 2 | import { withTrailingSlash } from 'ufo' 3 | 4 | import { prepareUseStatic } from './static' 5 | import { 6 | addResolvedTemplate, 7 | getNuxtGlobals, 8 | isFullStatic, 9 | isUrl, 10 | } from './utils' 11 | 12 | export function addGlobalsFile(this: ModuleThis) { 13 | const nuxtOptions = this.options 14 | 15 | const { staticPath } = prepareUseStatic.call(this) 16 | const { globalContext, globalNuxt } = getNuxtGlobals.call(this) 17 | 18 | const routerBase = withTrailingSlash(nuxtOptions.router.base) 19 | const publicPath = withTrailingSlash(nuxtOptions.build.publicPath) 20 | 21 | const globals = { 22 | // useFetch 23 | isFullStatic: isFullStatic(nuxtOptions), 24 | // useStatic 25 | staticPath, 26 | publicPath: isUrl(publicPath) ? publicPath : routerBase, 27 | // Throughout 28 | globalContext, 29 | globalNuxt, 30 | } 31 | 32 | const contents = Object.entries(globals) 33 | .map(([key, value]) => `export const ${key} = ${JSON.stringify(value)}`) 34 | .join('\n') 35 | 36 | const globalsFile = addResolvedTemplate.call(this, 'globals.mjs', { 37 | contents, 38 | }) 39 | 40 | nuxtOptions.alias['@nuxtjs/composition-api/dist/runtime/globals'] = 41 | globalsFile 42 | } 43 | -------------------------------------------------------------------------------- /docs/pages/en/2.packages/3.useMeta.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useMeta 3 | description: 'You can define your head and meta properties with the Nuxt Composition API.' 4 | --- 5 | 6 | You can interact directly with [head properties](https://nuxtjs.org/api/pages-head/) in `setup` (and within the `onGlobalSetup` method) by means of the `useMeta()` helper. 7 | 8 | :::alert 9 | In order to enable `useMeta`, please make sure you include `head: {}` within your component definition, and you are using the `defineComponent` exported from `@nuxtjs/composition-api`. 10 | ::: 11 | 12 | ```ts 13 | import { defineComponent, useMeta, computed, ref } from '@nuxtjs/composition-api' 14 | 15 | export default defineComponent({ 16 | // You need to define an empty head to activate this functionality 17 | head: {}, 18 | setup() { 19 | // This will allow you to set the title in head - but won't allow you to read its state outside of this component. 20 | const { title } = useMeta() 21 | title.value = 'My page' 22 | 23 | // You could also provide an initial value. 24 | const { title } = useMeta({ title: 'My page' }) 25 | 26 | // ... or simply set some meta tags 27 | useMeta({ title: 'My page', ... }) 28 | 29 | // You can even pass a function to achieve a computed meta 30 | const message = ref('') 31 | useMeta(() => ({ title: message.value })) 32 | }, 33 | }) 34 | ``` 35 | -------------------------------------------------------------------------------- /test/fixture/pages/meta.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 57 | -------------------------------------------------------------------------------- /docs/pages/en/6.API/1.useAsync.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useAsync 3 | description: 'You can define async functions that run once and persist the data on client-side.' 4 | --- 5 | 6 | You can create reactive values that depend on asynchronous calls with `useAsync`. 7 | 8 | On the server, this helper will inline the result of the async call in your HTML and automatically inject them into your client code. Much like `asyncData`, it _won't_ re-run these async calls client-side. 9 | 10 | However, if the call hasn't been carried out on SSR (such as if you have navigated to the page after initial load), it returns a `null` ref that is filled with the result of the async call when it resolves. 11 | 12 | The `$http` here has been taken from the [`@nuxt/http` module](https://http.nuxtjs.org/). In order for this example to work, you need to install it (`yarn add @nuxt/http`/`npm install @nuxt/http`) and add it to your `nuxt.config.js` modules list. 13 | 14 | ```ts 15 | import { defineComponent, useAsync, useContext } from '@nuxtjs/composition-api' 16 | 17 | export default defineComponent({ 18 | setup() { 19 | const { $http } = useContext() 20 | const posts = useAsync(() => $http.$get('/api/posts')) 21 | 22 | return { posts } 23 | }, 24 | }) 25 | ``` 26 | 27 | :::alert 28 | At the moment, `useAsync` is only suitable for one-offs, unless you provide your own unique key. [More information](/getting-started/gotchas#keyed-functions). 29 | ::: 30 | -------------------------------------------------------------------------------- /docs/pages/en/1.getting-started/2.setup.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Quick start 3 | description: 'Getting started with the Nuxt Composition API.' 4 | --- 5 | 6 | ## Quick start 7 | 8 | 1. First, install `@nuxtjs/composition-api`: 9 | 10 | :::::code-group 11 | ::::code-block{label="Yarn" active} 12 | 13 | ```bash 14 | yarn add @nuxtjs/composition-api 15 | ``` 16 | 17 | :::: 18 | ::::code-block{label="NPM"} 19 | 20 | ```bash 21 | npm install @nuxtjs/composition-api --save 22 | ``` 23 | 24 | :::: 25 | ::::: 26 | 27 | 2. Enable the module. 28 | 29 | ```js[nuxt.config.js] 30 | { 31 | buildModules: [ 32 | '@nuxtjs/composition-api/module' 33 | ] 34 | } 35 | ``` 36 | 37 | Note that [using `buildModules`](https://nuxtjs.org/api/configuration-modules#-code-buildmodules-code-) requires Nuxt >= 2.9. Just add it to your `modules` if you're on a lower version. 38 | 39 | 3. You're good to go! 40 | 41 | :::alert{type="info"} 42 | 43 | - The module automatically installs [`@vue/composition-api`](https://github.com/vuejs/composition-api) as a plugin, so you do not need to enable it separately. 44 | 45 | - For convenience, this package also exports the [`@vue/composition-api`](https://github.com/vuejs/composition-api) methods and hooks, so you can import directly from `@nuxtjs/composition-api`. 46 | 47 | - For better IDE auto-complete when using `script setup`, follow [these instructions](https://github.com/antfu/unplugin-vue2-script-setup#ide). 48 | 49 | ::: 50 | -------------------------------------------------------------------------------- /example/pages/index.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 55 | -------------------------------------------------------------------------------- /test/unit/__snapshots__/ssr-ref.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`ssrRef reactivity > ssrRefs react to change in state 1`] = ` 4 | { 5 | "nuxt": { 6 | "globalRefs": {}, 7 | "ssrRefs": { 8 | "name": "full name", 9 | }, 10 | }, 11 | } 12 | `; 13 | 14 | exports[`ssrRef reactivity > ssrRefs react to constructors 1`] = ` 15 | { 16 | "value": bound Object { 17 | "testMap": Map { 18 | "john" => "doe", 19 | }, 20 | }, 21 | } 22 | `; 23 | 24 | exports[`ssrRef reactivity > ssrRefs react to deep change in array state 1`] = ` 25 | { 26 | "nuxt": { 27 | "globalRefs": {}, 28 | "ssrRefs": { 29 | "obj": { 30 | "deep": { 31 | "object": [ 32 | { 33 | "name": "full name", 34 | }, 35 | ], 36 | }, 37 | }, 38 | }, 39 | }, 40 | } 41 | `; 42 | 43 | exports[`ssrRef reactivity > ssrRefs react to deep change in object state 1`] = ` 44 | { 45 | "nuxt": { 46 | "globalRefs": {}, 47 | "ssrRefs": { 48 | "obj": { 49 | "deep": { 50 | "object": { 51 | "name": "full name", 52 | }, 53 | }, 54 | }, 55 | }, 56 | }, 57 | } 58 | `; 59 | 60 | exports[`ssrRef reactivity > ssrRefs within multiple requests 1`] = ` 61 | { 62 | "nuxt": { 63 | "globalRefs": {}, 64 | "ssrRefs": { 65 | "concurrentRef": 2, 66 | }, 67 | }, 68 | } 69 | `; 70 | -------------------------------------------------------------------------------- /example/pages/posts/_id.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 62 | -------------------------------------------------------------------------------- /docs/pages/en/1.getting-started/1.introduction.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Introduction 3 | description: '@nuxtjs/composition-api provides a way to use the Vue Composition API with Nuxt-specific features.' 4 | --- 5 | 6 | > `@nuxtjs/composition-api` provides a way to use the Vue Composition API in with Nuxt-specific features. 7 | 8 | :::alert{type="info"} 9 | **Nuxt Bridge has now been released in beta.** It has full composition API support and it's strongly recommended to use Nuxt Bridge _instead_, if possible, by following the steps in [the Bridge migration guide](https://nuxt.com/docs/bridge/overview). Feedback welcome at [https://github.com/nuxt-community/composition-api/discussions/585](https://github.com/nuxt-community/composition-api/discussions/585). 10 | ::: 11 | 12 | ## Key features 13 | 14 | :::list{type="primary"} 15 | 16 | - Support for the new Nuxt fetch in v2.12+ 17 | - Easy access to router, app, store within setup() 18 | - Interact directly with your vue-meta properties within setup() 19 | - Drop-in replacement for ref with automatic SSR stringification and hydration (ssrRef) 20 | - Written in TypeScript 21 | - Use `script setup` (automatically configured using [`unplugin-vue2-script-setup`](https://github.com/antfu/unplugin-vue2-script-setup)) 22 | 23 | ::: 24 | 25 | ## API reference 26 | 27 | This package extends `@vue/composition-api` and exports all of its types and methods. You can find [a full reference for it in the Vue 3 docs](https://v3.vuejs.org/api/composition-api.html) and [in the initial Composition API RFC](https://composition-api.vuejs.org/api.html)). 28 | -------------------------------------------------------------------------------- /src/module/babel-register.ts: -------------------------------------------------------------------------------- 1 | import type { NuxtOptions } from '@nuxt/types' 2 | import type { ModuleThis } from '@nuxt/types/config/module' 3 | import { resolveRelativePath } from './utils' 4 | 5 | export function registerBabelPlugin(this: ModuleThis) { 6 | const nuxtOptions: NuxtOptions = this.nuxt.options 7 | 8 | /** 9 | * Add Composition API plugin to inject key automatically 10 | */ 11 | 12 | nuxtOptions.build.babel = nuxtOptions.build.babel || {} 13 | nuxtOptions.build.babel.plugins = nuxtOptions.build.babel.plugins || [] 14 | 15 | if (nuxtOptions.build.babel.plugins instanceof Function) { 16 | console.warn( 17 | 'Unable to automatically add Babel plugin. Make sure your custom `build.babel.plugins` returns `@nuxtjs/composition-api/dist/babel-plugin`' 18 | ) 19 | } else { 20 | nuxtOptions.build.babel.plugins.push(resolveRelativePath('../babel-plugin')) 21 | } 22 | 23 | /** 24 | * Opt in to Composition API support in Babel preset 25 | */ 26 | 27 | const actualPresets = nuxtOptions.build.babel.presets 28 | nuxtOptions.build.babel.presets = ( 29 | env, 30 | [defaultPreset, defaultOptions]: [string, Record] 31 | ) => { 32 | const newOptions = { 33 | ...defaultOptions, 34 | jsx: { 35 | ...(typeof defaultOptions.jsx === 'object' ? defaultOptions.jsx : {}), 36 | compositionAPI: true, 37 | }, 38 | } 39 | 40 | if (typeof actualPresets === 'function') { 41 | return actualPresets(env, [defaultPreset, newOptions]) 42 | } 43 | 44 | return [[defaultPreset, newOptions]] 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /.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@v4 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 | # Initializes the CodeQL tools for scanning. 25 | - name: Initialize CodeQL 26 | uses: github/codeql-action/init@v3 27 | 28 | # Override language selection by uncommenting this and choosing your languages 29 | # with: 30 | # languages: go, javascript, csharp, python, cpp, java 31 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 32 | # If this step fails, then you should remove it and run the build manually (see below) 33 | - name: Autobuild 34 | uses: github/codeql-action/autobuild@v3 35 | 36 | # ℹ️ Command-line programs to run using the OS shell. 37 | # 📚 https://git.io/JvXDl 38 | 39 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 40 | # and modify them (or add more) to build your code if your project 41 | # uses a compiled language 42 | 43 | #- run: | 44 | # make bootstrap 45 | # make release 46 | 47 | - name: Perform CodeQL Analysis 48 | uses: github/codeql-action/analyze@v3 49 | -------------------------------------------------------------------------------- /src/babel-plugin/index.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.Encoding = '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 'reqSsrRef': 41 | case 'useAsync': 42 | if (path.node.arguments.length > 1) return 43 | break 44 | 45 | default: 46 | return 47 | } 48 | 49 | const hash = crypto.createHash('md5') 50 | hash.update(`${cwd}-${path.node.callee.start}`) 51 | const digest = hash.digest(method).toString() 52 | path.node.arguments.push(t.stringLiteral(`${varName}${digest}`)) 53 | }, 54 | } 55 | return { visitor } 56 | } 57 | -------------------------------------------------------------------------------- /docs/pages/en/5.data/2.ssrPromise.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: ssrPromise 3 | description: 'You can create promises on the server that are resolved on the client' 4 | --- 5 | 6 | `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.) 7 | 8 | ```ts 9 | import { 10 | defineComponent, 11 | onBeforeMount, 12 | ref, 13 | ssrPromise, 14 | } from '@nuxtjs/composition-api' 15 | 16 | export default defineComponent({ 17 | setup() { 18 | const _promise = ssrPromise(async () => myAsyncFunction()) 19 | const resolvedPromise = ref(null) 20 | 21 | onBeforeMount(async () => { 22 | resolvedPromise.value = await _promise 23 | }) 24 | 25 | return { 26 | // On the server, this will be null until the promise resolves. 27 | // On the client, if server-rendered, this will always be the resolved promise. 28 | resolvedPromise, 29 | } 30 | }, 31 | }) 32 | ``` 33 | 34 | :::alert{type="info"} 35 | 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/module` 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/dist/babel-plugin` to your Babel plugins. 36 | ::: 37 | 38 | :::alert 39 | At the moment, an `ssrPromise` is only suitable for one-offs, unless you provide your own unique key. [More information](/getting-started/gotchas#keyed-functions). 40 | ::: 41 | -------------------------------------------------------------------------------- /docs/pages/en/4.lifecycle/2.useFetch.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useFetch 3 | description: 'You can access the Nuxt fetch() hook within the composition API.' 4 | --- 5 | 6 | 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. 7 | 8 | You can access this with this package as follows: 9 | 10 | ```ts 11 | import { defineComponent, ref, useFetch } from '@nuxtjs/composition-api' 12 | import axios from 'axios' 13 | 14 | export default defineComponent({ 15 | setup() { 16 | const name = ref('') 17 | 18 | const { fetch, fetchState } = useFetch(async () => { 19 | name.value = await axios.get('https://myapi.com/name') 20 | }) 21 | 22 | // Manually trigger a refetch 23 | fetch() 24 | 25 | // Access fetch error, pending and timestamp 26 | fetchState 27 | 28 | return { name } 29 | }, 30 | }) 31 | ``` 32 | 33 | :::alert 34 | `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. 35 | ::: 36 | 37 | :::alert{type="warning"} 38 | `useFetch` should be used with `ref`s and not `ssrRef`s because state serialization and hydration is already covered by `useFetch`. Else, the state would be sent from server to client "twice", via the `ssrRef` and via `useFetch` 39 | ::: 40 | 41 | :::alert{type="info"} 42 | `$fetch` and `$fetchState` will already be defined on the instance - so no need to return `fetch` or `fetchState` from setup. 43 | ::: 44 | 45 | :::alert{type="info"} 46 | Note that `useFetch` doesn't support use within `onGlobalSetup`. 47 | ::: 48 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /test/fixture/pages/ssr-ref.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 73 | -------------------------------------------------------------------------------- /src/runtime/composables/hooks.ts: -------------------------------------------------------------------------------- 1 | import type { Plugin } from '@nuxt/types' 2 | import type { SetupContext } from 'vue' 3 | import { getHeadOptions } from './meta' 4 | 5 | import { reqRefs } from './req-ref' 6 | import { setSSRContext } from './ssr-ref' 7 | 8 | type SetupFunction = ( 9 | this: void, 10 | props: Record, 11 | ctx: SetupContext 12 | ) => void | Record 13 | 14 | let globalSetup: Set 15 | 16 | /** 17 | * Run a callback function in the global setup function. This should be called from a Nuxt plugin. 18 | * @param fn The function to run in the setup function. It receives the global props and context. 19 | * @example 20 | ```ts 21 | import { onGlobalSetup } from '@nuxtjs/composition-api' 22 | 23 | export default () => { 24 | onGlobalSetup(() => { 25 | provide('globalKey', true) 26 | }) 27 | } 28 | ``` 29 | */ 30 | export const onGlobalSetup = (fn: SetupFunction) => { 31 | globalSetup.add(fn) 32 | } 33 | 34 | /** 35 | * 36 | * @private 37 | */ 38 | export const setMetaPlugin: Plugin = context => { 39 | const { head } = context.app 40 | Object.assign(context.app, getHeadOptions({ head: head as any })) 41 | } 42 | 43 | /** 44 | * @private 45 | */ 46 | export const globalPlugin: Plugin = context => { 47 | if (process.server) { 48 | reqRefs.forEach(reset => reset()) 49 | setSSRContext(context.app) 50 | } 51 | 52 | const { setup } = context.app 53 | globalSetup = new Set() 54 | 55 | context.app.setup = function (...args) { 56 | let result = {} 57 | if (setup instanceof Function) { 58 | result = setup(...args) || {} 59 | } 60 | for (const fn of globalSetup) { 61 | result = { ...result, ...(fn.call(this, ...args) || {}) } 62 | } 63 | return result 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 t.wait(4000) 31 | if (process.env.GENERATE) { 32 | await expectNotOnPage('loading email') 33 | await expectNotOnPage('long@load.com') 34 | } else { 35 | await expectOnPage('long@load.com') 36 | await expectNotOnPage('loading email') 37 | } 38 | }) 39 | 40 | test('Refetches with $fetch', async t => { 41 | await navigateTo('/') 42 | await expectNotOnPage('loading email') 43 | await t.click(Selector('button')) 44 | await expectOnPage('loading email') 45 | }) 46 | 47 | test('TTFB is lower than 100ms', async t => { 48 | await navigateTo('/ttfb') 49 | const ttfbRegex = /TTFB: (\d+)ms/ 50 | const selector = await Selector('*').withText(new RegExp(ttfbRegex, 'i')) 51 | const text = await selector.innerText 52 | const [, msString] = /TTFB: (\d+)ms/.exec(text)! 53 | const ms = Number(msString) 54 | await t.expect(ms).lte(120) 55 | }) 56 | 57 | test("Doesn't overwrite methods and getters", async () => { 58 | await navigateTo('/') 59 | await expectOnPage('computed') 60 | await expectOnPage('function result') 61 | }) 62 | -------------------------------------------------------------------------------- /docs/pages/en/1.getting-started/3.gotchas.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Gotchas 3 | description: 'There are a couple of points that you should be aware of when using `@nuxtjs/composition-api`.' 4 | --- 5 | 6 | ## **'Keyed' functions** 7 | 8 | A number of helper functions use a key to pass JSON-encoded information from server to client (currently `shallowSsrRef`, `ssrPromise`, `ssrRef` and `useAsync`). 9 | 10 | In order to make that possible, under the hood this library adds a key based on line number within your code. 11 | 12 | If you want to use these functions within global composables, make sure to set a unique key based on each calling of the function - for example, you might key it to the route path. (Each function takes an optional second parameter that is this key.) 13 | 14 | If you don't provide a unique key, this is the consequence: 15 | 16 | ```ts 17 | function useMyFeature() { 18 | // Only one unique key is generated, no matter 19 | // how many times this function is called. 20 | const feature = ssrRef('') 21 | 22 | return feature 23 | } 24 | 25 | const a = useMyFeature() 26 | const b = useMyFeature() 27 | 28 | b.value = 'changed' 29 | // On client-side, a's value will also be initialised to 'changed' 30 | ``` 31 | 32 | Here is how you might implement it by setting a key. 33 | 34 | ```ts 35 | function useMyFeature(path: string) { 36 | const content = useAsync( 37 | () => fetch(`https://api.com/slug/${path}`).then(r => r.json()), 38 | path 39 | ) 40 | 41 | return { 42 | content, 43 | } 44 | } 45 | 46 | export default useMyFeature 47 | ``` 48 | 49 | ## **Shared server state** 50 | 51 | If you are declaring refs in the global state of your application - such as within plugins or in state/store files (for example, as a replacement for Vuex) - you should be aware that these refs are persisted across requests when your site is in production mode. 52 | 53 | You should take especial care with declaring refs in this way. 54 | -------------------------------------------------------------------------------- /.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 | concurrency: 12 | group: ${{ github.workflow }}-${{ github.event.number || github.sha }} 13 | cancel-in-progress: ${{ github.event_name != 'push' }} 14 | 15 | jobs: 16 | lint: 17 | runs-on: ubuntu-latest 18 | 19 | steps: 20 | - uses: actions/checkout@v4 21 | - run: corepack enable 22 | - uses: actions/setup-node@v4 23 | with: 24 | node-version: 20 25 | cache: "yarn" 26 | 27 | - name: Install dependencies 28 | run: yarn 29 | 30 | - name: Lint project 31 | run: yarn lint 32 | 33 | e2e: 34 | strategy: 35 | matrix: 36 | os: 37 | - ubuntu-latest 38 | - windows-latest 39 | test: 40 | - e2e-generated 41 | - e2e-globals 42 | - e2e-ssr 43 | runs-on: "${{ matrix.os }}" 44 | 45 | steps: 46 | - uses: actions/checkout@v4 47 | - run: corepack enable 48 | - uses: actions/setup-node@v4 49 | with: 50 | node-version: 20 51 | cache: "yarn" 52 | 53 | - name: Install dependencies 54 | run: yarn 55 | 56 | - name: Build project 57 | run: yarn build 58 | 59 | - name: Test project 60 | run: yarn test:${{ matrix.test }} 61 | env: 62 | TEST_BUILT_MODULE: true 63 | 64 | unit: 65 | strategy: 66 | matrix: 67 | os: 68 | - ubuntu-latest 69 | - windows-latest 70 | runs-on: "${{ matrix.os }}" 71 | 72 | steps: 73 | - uses: actions/checkout@v4 74 | - run: corepack enable 75 | - uses: actions/setup-node@v4 76 | with: 77 | node-version: 20 78 | cache: "yarn" 79 | 80 | - name: Install dependencies 81 | run: yarn 82 | 83 | - name: Build project 84 | run: yarn build 85 | 86 | - name: Test types 87 | run: yarn test:types 88 | 89 | - name: Unit tests 90 | run: yarn test:unit 91 | -------------------------------------------------------------------------------- /docs/pages/en/6.API/3.useStatic.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useStatic 3 | description: 'You can define async calls that will be produce static JSON on site generation.' 4 | --- 5 | 6 | You can pre-run expensive functions using `useStatic`. 7 | 8 | ```ts 9 | import { 10 | defineComponent, 11 | useContext, 12 | useStatic, 13 | computed, 14 | } from '@nuxtjs/composition-api' 15 | import axios from 'axios' 16 | 17 | export default defineComponent({ 18 | setup() { 19 | const { params } = useContext() 20 | const id = computed(() => params.value.id) 21 | const post = useStatic( 22 | id => axios.get(`https://jsonplaceholder.typicode.com/posts/${id}`), 23 | id, 24 | 'post' 25 | ) 26 | 27 | return { post } 28 | }, 29 | }) 30 | ``` 31 | 32 | ## SSG 33 | 34 | 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: 35 | 36 | - On generate, the result of a `useStatic` call will be saved to a JSON file and copied into the `/dist` directory. 37 | - On hard-reload of a generated page, the JSON will be inlined into the page and cached. 38 | - 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. 39 | 40 | :::alert 41 | If you are pregenerating some pages in your app note that you may need to increase `generate.interval`. (See [setup instructions](/getting-started/setup).) 42 | :::alert 43 | 44 | ## SSR 45 | 46 | If the route is not pre-generated (including in dev mode), then: 47 | 48 | - 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. 49 | - On client navigation, the client will run the factory function. 50 | 51 | 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. 52 | -------------------------------------------------------------------------------- /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('button').withText('Set')) 19 | await t 20 | .expect(Selector('meta[name=message]').getAttribute('content')) 21 | .eql('new message') 22 | // await t 23 | // .expect(Selector('meta[name=viewport]').getAttribute('content')) 24 | // .eql('width=device-width, initial-scale=1') 25 | await t.expect(Selector('noscript').textContent).eql('Test') 26 | 27 | await t.click(Selector('a').withText('back')) 28 | await t.expect(Selector('title').innerText).eql('My fixture - fixture') 29 | }) 30 | 31 | test('Shows correct title on client-loaded page', async t => { 32 | await navigateTo('/') 33 | await t.expect(Selector('title').innerText).eql('My fixture - fixture') 34 | 35 | await t.click(Selector('a').withText('meta')) 36 | await t.expect(Selector('title').innerText).eql('newSetTitle - fixture') 37 | 38 | await t.expect(Selector('title').innerText).eql('mounted title - fixture') 39 | 40 | await t.click(Selector('button').withText('Change')) 41 | await t.expect(Selector('title').innerText).eql('mounted title - Changed') 42 | 43 | await t.click(Selector('button').withText('Set')) 44 | await t 45 | .expect(Selector('meta[name=message]').getAttribute('content')) 46 | .eql('new message') 47 | // await t 48 | // .expect(Selector('meta[name=viewport]').getAttribute('content')) 49 | // .eql('width=device-width, initial-scale=1') 50 | await t.expect(Selector('noscript').textContent).eql('Test') 51 | }) 52 | -------------------------------------------------------------------------------- /test/fixture/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | import { join, resolve } from 'pathe' 2 | import type { NuxtConfig } from '@nuxt/types' 3 | 4 | const routes = ['/route/a', '/static/1', '/static/2', '/static/3'] 5 | const interval = 3000 6 | 7 | const isGenerated = process.env.GENERATE === 'true' 8 | const isTesting = process.env.NODE_ENV !== 'development' 9 | 10 | const rootDir = resolve(__dirname, '../..') 11 | 12 | const inDevelopment = !process.env.TEST_BUILT_MODULE 13 | const moduleSource = join(rootDir, inDevelopment ? 'src' : 'dist') 14 | 15 | console.log('Testing', inDevelopment ? 'source' : 'built', 'module') 16 | 17 | export default { 18 | alias: { 19 | '@nuxtjs/composition-api/dist/runtime/globals': join( 20 | moduleSource, 21 | 'runtime/globals' 22 | ), 23 | '@nuxtjs/composition-api/dist/babel-plugin': join( 24 | moduleSource, 25 | 'babel-plugin' 26 | ), 27 | '@nuxtjs/composition-api': join(moduleSource, 'runtime'), 28 | }, 29 | target: isGenerated ? 'static' : 'server', 30 | publicRuntimeConfig: { 31 | globalInject: 'injected', 32 | }, 33 | rootDir, 34 | srcDir: __dirname, 35 | plugins: ['~/plugins/global.js'], 36 | serverMiddleware: [ 37 | { 38 | path: '/api/posts', 39 | handler: '~/api/posts', 40 | }, 41 | ], 42 | head: { 43 | titleTemplate: title => `${title} - fixture`, 44 | noscript: [ 45 | { 46 | innerHTML: 'Test', 47 | }, 48 | ], 49 | link: isTesting 50 | ? [] 51 | : [ 52 | { 53 | rel: 'stylesheet', 54 | href: 'https://newcss.net/lite.css', 55 | }, 56 | ], 57 | }, 58 | ...(process.env.GLOBALS === 'true' 59 | ? { 60 | globalName: 'bob', 61 | globals: { 62 | nuxt: globalName => `$my${globalName}`, 63 | }, 64 | } 65 | : {}), 66 | generate: { 67 | dir: 'test/fixture/dist', 68 | crawler: false, 69 | routes, 70 | interval, 71 | }, 72 | buildModules: [ 73 | '@nuxt/typescript-build', 74 | '@nuxtjs/pwa', 75 | join(moduleSource, 'module'), 76 | ], 77 | pwa: { 78 | icon: false, 79 | manifest: false, 80 | workbox: false, 81 | }, 82 | } 83 | -------------------------------------------------------------------------------- /src/module/utils.ts: -------------------------------------------------------------------------------- 1 | import { NuxtConfig, NuxtOptions } from '@nuxt/types' 2 | import { ModuleThis } from '@nuxt/types/config/module' 3 | import { join, resolve } from 'pathe' 4 | 5 | export function isFullStatic(options: NuxtConfig) { 6 | return ( 7 | !options.dev && 8 | !options._legacyGenerate && 9 | options.target === 'static' && 10 | options.render?.ssr 11 | ) 12 | } 13 | 14 | export function isUrl(url: string) { 15 | return ['http', '//'].some(str => url.startsWith(str)) 16 | } 17 | 18 | export function resolveRelativePath(...path: string[]) { 19 | return resolve(__dirname, ...path) 20 | } 21 | 22 | export function addResolvedTemplate( 23 | this: ModuleThis, 24 | template: string, 25 | options: Record = {} 26 | ) { 27 | const nuxtOptions: NuxtOptions = this.nuxt.options 28 | 29 | const src = resolveRelativePath(`../runtime/templates/${template}`) 30 | const { dst } = this.addTemplate({ 31 | src, 32 | fileName: join('composition-api', template), 33 | options, 34 | }) 35 | 36 | const templatePath = join(nuxtOptions.buildDir, dst) 37 | 38 | return templatePath 39 | } 40 | 41 | export function resolveCoreJsVersion(this: ModuleThis) { 42 | let corejsPolyfill = this.nuxt.options.build.corejs 43 | ? String(this.nuxt.options.build.corejs) 44 | : undefined 45 | try { 46 | if (!['2', '3'].includes(corejsPolyfill || '')) { 47 | // eslint-disable-next-line 48 | const corejsPkg = this.nuxt.resolver.requireModule('core-js/package.json') 49 | corejsPolyfill = corejsPkg.version.slice(0, 1) 50 | } 51 | } catch { 52 | corejsPolyfill = undefined 53 | } 54 | return corejsPolyfill 55 | } 56 | 57 | export function getNuxtGlobals(this: ModuleThis) { 58 | const nuxtOptions: NuxtOptions = this.nuxt.options 59 | 60 | const globalName = nuxtOptions.globalName 61 | const globalContextFactory = 62 | nuxtOptions.globals.context || 63 | ((globalName: string) => `__${globalName.toUpperCase()}__`) 64 | const globalNuxtFactory = 65 | nuxtOptions.globals.nuxt || ((globalName: string) => `$${globalName}`) 66 | const globalContext = globalContextFactory(globalName) 67 | const globalNuxt = globalNuxtFactory(globalName) 68 | 69 | return { globalContext, globalNuxt } 70 | } 71 | -------------------------------------------------------------------------------- /test/fixture/pages/index.vue: -------------------------------------------------------------------------------- 1 | 48 | 49 | 76 | -------------------------------------------------------------------------------- /src/runtime/composables/async.ts: -------------------------------------------------------------------------------- 1 | import { isRef, onServerPrefetch } from 'vue' 2 | import type { Ref } from 'vue' 3 | 4 | import { globalNuxt } from '@nuxtjs/composition-api/dist/runtime/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/module` 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/dist/babel-plugin` 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 === null || 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 happy-dom 3 | */ 4 | import { describe, beforeEach, test, expect, vi } from 'vitest' 5 | import { ssrRef, globalPlugin } from '../../src/runtime/composables' 6 | 7 | let ssrContext: Record 8 | 9 | // eslint-disable-next-line @typescript-eslint/no-empty-function 10 | function dummyInject() {} 11 | 12 | vi.mock('../../src/runtime/composables/utils', async () => { 13 | const utils = await vi.importActual< 14 | typeof import('../../src/runtime/composables/utils') 15 | >('../../src/runtime/composables/utils') 16 | return { 17 | ...utils, 18 | getCurrentInstance: vi.fn().mockImplementation(() => ({ 19 | $nuxt: { 20 | context: { ssrContext }, 21 | }, 22 | })), 23 | } 24 | }) 25 | 26 | describe('ssrRef reactivity', () => { 27 | beforeEach(async () => { 28 | process.server = true 29 | ssrContext = Object.assign({}, { nuxt: {} }) 30 | globalPlugin({ app: { context: { ssrContext } } } as any, dummyInject) 31 | }) 32 | test('ssrRefs react to change in state', async () => { 33 | process.client = false 34 | const name = ssrRef('', 'name') 35 | ssrRef('', 'unchanged') 36 | name.value = 'full name' 37 | expect(ssrContext).toMatchSnapshot() 38 | }) 39 | test('ssrRefs react to deep change in object state', async () => { 40 | process.client = false 41 | const obj = ssrRef({ deep: { object: { name: 'nothing' } } }, 'obj') 42 | obj.value.deep.object.name = 'full name' 43 | expect(ssrContext).toMatchSnapshot() 44 | }) 45 | test('ssrRefs react to deep change in array state', async () => { 46 | process.client = false 47 | const obj = ssrRef({ deep: { object: [{ name: 'nothing' }] } }, 'obj') 48 | obj.value.deep.object[0].name = 'full name' 49 | expect(ssrContext).toMatchSnapshot() 50 | }) 51 | 52 | test('ssrRefs within multiple requests', async () => { 53 | const concurrentRef = ssrRef(1, 'concurrentRef') 54 | 55 | // simulate the new request comes in 56 | globalPlugin( 57 | { app: { context: { ssrContext: { nuxt: {} } } } } as any, 58 | dummyInject 59 | ) 60 | 61 | concurrentRef.value = 2 62 | 63 | expect(ssrContext).toMatchSnapshot() 64 | }) 65 | 66 | test('ssrRefs react to constructors', async () => { 67 | const testMap = new Map() 68 | testMap.set('john', 'doe') 69 | 70 | const obj = ssrRef({ testMap }, 'obj') 71 | 72 | expect(obj).toMatchSnapshot() 73 | }) 74 | }) 75 | -------------------------------------------------------------------------------- /src/runtime/composables/context.ts: -------------------------------------------------------------------------------- 1 | import { computed } from 'vue' 2 | 3 | import type { Ref } from 'vue' 4 | import type { Context } from '@nuxt/types' 5 | import type { Route } from 'vue-router' 6 | 7 | import { globalNuxt } from '@nuxtjs/composition-api/dist/runtime/globals' 8 | import { getCurrentInstance } from './utils' 9 | 10 | interface ContextCallback { 11 | (context: Context): void 12 | } 13 | 14 | /** 15 | * @deprecated 16 | * Recommend using `useContext` instead 17 | */ 18 | export const withContext = (callback: ContextCallback) => { 19 | const vm = getCurrentInstance() 20 | if (!vm) throw new Error('This must be called within a setup function.') 21 | 22 | callback((vm[globalNuxt] || vm.$options).context) 23 | } 24 | 25 | interface UseContextReturn 26 | extends Omit { 27 | route: Ref 28 | query: Ref 29 | from: Ref 30 | params: Ref 31 | } 32 | 33 | /** 34 | * `useContext` will return the Nuxt context. 35 | * @example 36 | ```ts 37 | import { defineComponent, ref, useContext } from '@nuxtjs/composition-api' 38 | 39 | export default defineComponent({ 40 | setup() { 41 | const { store } = useContext() 42 | store.dispatch('myAction') 43 | }, 44 | }) 45 | ``` 46 | */ 47 | export const useContext = (): UseContextReturn => { 48 | const vm = getCurrentInstance() 49 | if (!vm) throw new Error('This must be called within a setup function.') 50 | 51 | return { 52 | ...(vm[globalNuxt] || vm.$options).context, 53 | /** 54 | * @deprecated To smooth your upgrade to Nuxt 3, it is recommended not to access `route` from `useContext` but rather to use the `useRoute` helper function. 55 | */ 56 | route: computed(() => vm.$route), 57 | /** 58 | * @deprecated To smooth your upgrade to Nuxt 3, it is recommended not to access `query` from `useContext` but rather to use the `useRoute` helper function. 59 | */ 60 | query: computed(() => vm.$route.query), 61 | /** 62 | * @deprecated To smooth your upgrade to Nuxt 3, it is recommended not to access `from` from `useContext` but rather to use the `useRoute` helper function. 63 | */ 64 | from: computed(() => (vm[globalNuxt] || vm.$options).context.from), 65 | /** 66 | * @deprecated To smooth your upgrade to Nuxt 3, it is recommended not to access `params` from `useContext` but rather to use the `useRoute` helper function. 67 | */ 68 | params: computed(() => vm.$route.params), 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /docs/pages/en/5.data/3.ssrRef.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: ssrRef, shallowSsrRef 3 | description: 'You can define data on the server that is stringified and hydrated on client-side.' 4 | --- 5 | 6 | When creating composition utility functions, often there will be server-side state that needs to be conveyed to the client. 7 | 8 | :::alert 9 | If initialised within `setup()` or via `onGlobalSetup`, `ssrRef` data will exist _only_ within the request state. If initialised _outside_ a component there is the possibility that an `ssrRef` may share state across requests. 10 | ::: 11 | 12 | ## ssrRef 13 | 14 | `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. 15 | 16 | ```ts 17 | import { ssrRef } from '@nuxtjs/composition-api' 18 | 19 | const val = ssrRef('') 20 | 21 | // When hard-reloaded, `val` will be initialised to 'server set' 22 | if (process.server) val.value = 'server set' 23 | 24 | // When hard-reloaded, the result of myExpensiveSetterFunction() will 25 | // be encoded in nuxtState and used as the initial value of this ref. 26 | // If client-loaded, the setter function will run to come up with initial value. 27 | const val2 = ssrRef(myExpensiveSetterFunction) 28 | ``` 29 | 30 | :::alert{type="info"} 31 | 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/module` 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/dist/babel-plugin` to your Babel plugins. 32 | ::: 33 | 34 | :::alert 35 | At the moment, an `ssrRef` is only suitable for one-offs, unless you provide your own unique key. [More information](/getting-started/gotchas#keyed-functions). 36 | ::: 37 | 38 | ## shallowSsrRef 39 | 40 | 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. 41 | 42 | ```ts 43 | import { shallowSsrRef, onMounted } from '@nuxtjs/composition-api' 44 | 45 | const shallow = shallowSsrRef({ v: 'init' }) 46 | if (process.server) shallow.value = { v: 'changed' } 47 | 48 | // On client-side, shallow.value will be { v: changed } 49 | onMounted(() => { 50 | // This and other changes outside of setup won't trigger component updates. 51 | shallow.value.v = 'Hello World' 52 | }) 53 | ``` 54 | 55 | :::alert 56 | At the moment, a `shallowSsrRef` is only suitable for one-offs, unless you provide your own unique key. [More information](/getting-started/gotchas#keyed-functions). 57 | ::: 58 | -------------------------------------------------------------------------------- /src/module/vite-plugin.ts: -------------------------------------------------------------------------------- 1 | import crypto from 'crypto' 2 | import MagicString from 'magic-string' 3 | import { walk } from 'estree-walker' 4 | import type { Plugin } from 'rollup' 5 | 6 | function createKey( 7 | source: string, 8 | method: crypto.BinaryToTextEncoding = 'base64' 9 | ) { 10 | const hash = crypto.createHash('md5') 11 | hash.update(source) 12 | return hash.digest(method).toString() 13 | } 14 | 15 | export function compositionApiPlugin(): Plugin & { enforce: 'pre' } { 16 | return { 17 | name: 'nuxt:composition-api', 18 | enforce: 'pre', 19 | transform(code: string, filename: string) { 20 | const keyedFunctions = 21 | /(useStatic|shallowSsrRef|ssrPromise|ssrRef|reqSsrRef|useAsync)/ 22 | if (!keyedFunctions.test(code)) { 23 | return { 24 | code, 25 | map: null, 26 | } 27 | } 28 | 29 | try { 30 | const { 0: script = code, index: codeIndex = 0 } = 31 | code.match(/(?<=]*>)[\S\s.]*?(?=<\/script>)/) || {} 32 | const ast = this.parse(script) 33 | const s = new MagicString(code) 34 | 35 | walk(ast, { 36 | enter(node) { 37 | const { end } = node as unknown as { 38 | end: number 39 | } 40 | const { callee, arguments: args = [] } = node as { 41 | callee?: { 42 | type?: string 43 | name?: string 44 | property?: { type: string; name: string } 45 | } 46 | arguments?: any[] 47 | } 48 | if ( 49 | callee?.type === 'Identifier' || 50 | callee?.property?.type === 'Identifier' 51 | ) { 52 | let method: crypto.BinaryToTextEncoding = 'base64' 53 | 54 | switch (callee.name || callee.property?.name) { 55 | case 'useStatic': 56 | if (args.length > 2) return 57 | if (args.length === 2) { 58 | s.prependLeft(codeIndex + end - 1, ', undefined') 59 | } 60 | method = 'hex' 61 | break 62 | 63 | case 'shallowSsrRef': 64 | case 'ssrPromise': 65 | case 'ssrRef': 66 | case 'reqSsrRef': 67 | case 'useAsync': 68 | if (args.length > 1) return 69 | break 70 | 71 | default: 72 | return 73 | } 74 | s.appendLeft( 75 | codeIndex + end - 1, 76 | ", '" + createKey(`${filename}-${end}`, method) + "'" 77 | ) 78 | } 79 | }, 80 | }) 81 | 82 | return { 83 | code: s.toString(), 84 | map: s.generateMap({ source: filename }).toString(), 85 | } 86 | } catch {} 87 | }, 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

🏗️ Nuxt Composition API

2 |

Composition API hooks for Nuxt 2

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 Composition API with Nuxt-specific features. 26 | 27 | --- 28 | 29 | **Nuxt Bridge has now been released in beta.** It has full composition API support and it's strongly recommended to migrate from `@nuxtjs/composition-api`, if possible, by following the steps in [the Bridge migration guide](https://nuxt.com/docs/bridge/overview). Feedback welcome at `https://github.com/nuxt-community/composition-api/discussions/585`. 30 | 31 | --- 32 | 33 | ## Features 34 | 35 | - 🏃 **Fetch**: Support for the new Nuxt `fetch()` in v2.12+ 36 | - ℹ️ **Context**: Easy access to `router`, `app`, `store` within `setup()` 37 | - 🗺️ **Head**: Interact directly with your `vue-meta` properties within `setup()` 38 | - ✨ **Automatic hydration**: Drop-in replacement for `ref` with automatic SSR stringification and hydration (`ssrRef`) 39 | - 📝 **SSR support**: Allows using the Composition API with SSR 40 | - 💪 **TypeScript**: Written in TypeScript 41 | 42 |

43 | Read Documentation 44 |

45 | 46 | ## Contributors 47 | 48 | Contributions are very welcome. 49 | 50 | 1. Clone this repo 51 | 52 | ```bash 53 | git clone git@github.com:nuxt-community/composition-api.git 54 | ``` 55 | 56 | 2. Install dependencies and build project 57 | 58 | ```bash 59 | yarn 60 | # Compile library and watch for changes 61 | yarn watch 62 | # Start a test Nuxt fixture with hot reloading 63 | yarn fixture 64 | # Test 65 | yarn test 66 | ``` 67 | 68 | **Tip:** You can also use `yarn link` to test the module locally with your Nuxt project. 69 | 70 | ## License 71 | 72 | [MIT License](./LICENCE) - Copyright © Daniel Roe 73 | -------------------------------------------------------------------------------- /src/runtime/composables/wrappers.ts: -------------------------------------------------------------------------------- 1 | import { computed, ComputedRef, InjectionKey } from 'vue' 2 | import type { Store } from 'vuex' 3 | 4 | import { getCurrentInstance } from './utils' 5 | 6 | /** 7 | * You might want to create a custom helper to 'convert' a non-Composition API property to a Composition-ready one. `wrapProperty` enables you to do that easily, returning either a computed or a bare property as required. 8 | * @param property the name of the property you would like to access. For example, `$store` to access `this.$store`. 9 | * @param makeComputed a boolean indicating whether the helper function should return a computed property or not. Defaults to `true`. 10 | */ 11 | export const wrapProperty = < 12 | K extends keyof NonNullable>, 13 | T extends boolean = true 14 | >( 15 | property: K, 16 | makeComputed?: T 17 | ) => { 18 | return (): T extends true 19 | ? ComputedRef>[K]> 20 | : NonNullable>[K] => { 21 | const vm = getCurrentInstance() 22 | if (!vm) throw new Error('This must be called within a setup function.') 23 | 24 | return makeComputed !== false 25 | ? (computed(() => vm[property]) as any) 26 | : vm[property] 27 | } 28 | } 29 | 30 | /** 31 | * Gain access to the router just like using this.$router in a non-Composition API manner. 32 | * @example 33 | ```ts 34 | import { defineComponent, useRouter } from '@nuxtjs/composition-api' 35 | 36 | export default defineComponent({ 37 | setup() { 38 | const router = useRouter() 39 | router.push('/') 40 | } 41 | }) 42 | ``` 43 | */ 44 | export const useRouter = wrapProperty('$router', false) 45 | 46 | /** 47 | * Returns `this.$route`, wrapped in a computed - so accessible from `.value`. 48 | * @example 49 | ```ts 50 | import { computed, defineComponent, useRoute } from '@nuxtjs/composition-api' 51 | 52 | export default defineComponent({ 53 | setup() { 54 | const route = useRoute() 55 | const id = computed(() => route.value.params.id) 56 | } 57 | }) 58 | ``` 59 | */ 60 | export const useRoute = wrapProperty('$route') 61 | 62 | /** 63 | * Gain access to the store just like using this.$store in a non-Composition API manner. You can also provide an injection key or custom type to get back a semi-typed store: 64 | * @example 65 | ```ts 66 | import { defineComponent, useStore } from '@nuxtjs/composition-api' 67 | 68 | export interface State { 69 | count: number 70 | } 71 | 72 | export const key: InjectionKey> = Symbol() 73 | 74 | export default defineComponent({ 75 | setup() { 76 | const store = useStore() 77 | const store = useStore(key) 78 | const store = useStore() 79 | // In both of these cases, store.state.count will be typed as a number 80 | } 81 | }) 82 | ``` 83 | */ 84 | // eslint-disable-next-line 85 | export const useStore = (key?: InjectionKey): Store => { 86 | const vm = getCurrentInstance() 87 | if (!vm) throw new Error('This must be called within a setup function.') 88 | 89 | return vm.$store 90 | } 91 | -------------------------------------------------------------------------------- /src/runtime/composables/vue.ts: -------------------------------------------------------------------------------- 1 | export type { 2 | AsyncComponent, 3 | Component, 4 | ComponentComputedOptions, 5 | ComponentCustomOptions, 6 | ComponentCustomProperties, 7 | ComponentCustomProps, 8 | ComponentInstance, 9 | ComponentMethodOptions, 10 | ComponentOptions, 11 | ComponentPropsOptions, 12 | ComponentPublicInstance, 13 | ComputedGetter, 14 | ComputedOptions, 15 | ComputedRef, 16 | ComputedSetter, 17 | CreateComponentPublicInstance, 18 | CreateElement, 19 | CustomRefFactory, 20 | DebuggerEvent, 21 | Data, 22 | DebuggerEventExtraInfo, 23 | DebuggerOptions, 24 | DeepReadonly, 25 | Directive, 26 | DirectiveBinding, 27 | DirectiveFunction, 28 | DirectiveHook, 29 | DirectiveModifiers, 30 | DirectiveOptions, 31 | EffectScope, 32 | ExtractDefaultPropTypes, 33 | ExtractPropTypes, 34 | FunctionDirective, 35 | FunctionalComponentOptions, 36 | InferDefault, 37 | InferDefaults, 38 | InjectionKey, 39 | NotUndefined, 40 | ObjectDirective, 41 | PluginFunction, 42 | PluginObject, 43 | PropOptions, 44 | PropsWithDefaults, 45 | PropType, 46 | ReactiveFlags, 47 | Ref, 48 | RefUnwrapBailTypes, 49 | RenderContext, 50 | SetupContext, 51 | SetupFunction, 52 | ShallowReactive, 53 | ShallowRef, 54 | ShallowUnwrapRef, 55 | ToRef, 56 | ToRefs, 57 | TrackOpTypes, 58 | TriggerOpTypes, 59 | UnwrapNestedRefs, 60 | UnwrapRef, 61 | VNode, 62 | VNodeChildren, 63 | VNodeChildrenArrayContents, 64 | VNodeComponentOptions, 65 | VNodeData, 66 | VNodeDirective, 67 | VueConstructor, 68 | WatchCallback, 69 | WatchEffect, 70 | WatchHandler, 71 | WatchOptions, 72 | WatchOptionsBase, 73 | WatchOptionsWithHandler, 74 | WatchSource, 75 | WatchStopHandle, 76 | WritableComputedOptions, 77 | WritableComputedRef, 78 | } from 'vue' 79 | 80 | export { defineComponent } from './component' 81 | 82 | export { 83 | computed, 84 | customRef, 85 | del, 86 | effectScope, 87 | getCurrentInstance, 88 | getCurrentScope, 89 | h, 90 | inject, 91 | isProxy, 92 | isShallow, 93 | isReactive, 94 | isReadonly, 95 | isRef, 96 | markRaw, 97 | nextTick, 98 | onActivated, 99 | onBeforeMount, 100 | onBeforeUnmount, 101 | onBeforeUpdate, 102 | onDeactivated, 103 | onErrorCaptured, 104 | onMounted, 105 | onRenderTracked, 106 | onRenderTriggered, 107 | onScopeDispose, 108 | onServerPrefetch, 109 | onUnmounted, 110 | onUpdated, 111 | provide, 112 | proxyRefs, 113 | reactive, 114 | readonly, 115 | ref, 116 | set, 117 | shallowReactive, 118 | shallowReadonly, 119 | shallowRef, 120 | toRaw, 121 | toRef, 122 | toRefs, 123 | triggerRef, 124 | unref, 125 | useAttrs, 126 | useCssModule as useCSSModule, 127 | useCssModule, 128 | useCssVars, 129 | useSlots, 130 | version, 131 | watch, 132 | watchEffect, 133 | watchPostEffect, 134 | watchSyncEffect, 135 | } from 'vue' 136 | 137 | export const warn = () => console.warn('`warn` is not exported by Vue 2.7') 138 | export const createApp = () => 139 | console.warn('`createApp` is not exported by Vue 2.7') 140 | export const createRef = () => 141 | console.warn('`createRef` is not exported by Vue 2.7') 142 | export const defineAsyncComponent = () => 143 | console.warn('`defineAsyncComponent` is not exported by Vue 2.7') 144 | export const isRaw = () => console.warn('`isRaw` is not exported by Vue 2.7') 145 | -------------------------------------------------------------------------------- /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/runtime/composables/meta.ts: -------------------------------------------------------------------------------- 1 | import defu from 'defu' 2 | import { 3 | computed, 4 | reactive, 5 | ref, 6 | toRefs, 7 | watch, 8 | Ref, 9 | isReactive, 10 | isRef, 11 | toRaw, 12 | UnwrapRef, 13 | } from 'vue' 14 | 15 | import type { MetaInfo } from 'vue-meta' 16 | 17 | import { getCurrentInstance } from './utils' 18 | 19 | export type ReactiveHead> = UnwrapRef< 20 | Ref 21 | > 22 | 23 | type MetaInfoMapper = { 24 | [P in keyof T]: P extends 'base' 25 | ? T[P] | undefined 26 | : T[P] extends () => any 27 | ? T[P] | undefined 28 | : T[P] extends Array | Record 29 | ? T[P] 30 | : T[P] | undefined 31 | } 32 | 33 | export function createEmptyMeta(): MetaInfoMapper> { 34 | return { 35 | titleTemplate: null as unknown as undefined, 36 | 37 | __dangerouslyDisableSanitizers: [], 38 | __dangerouslyDisableSanitizersByTagID: {}, 39 | 40 | title: undefined, 41 | htmlAttrs: {}, 42 | headAttrs: {}, 43 | bodyAttrs: {}, 44 | 45 | base: undefined, 46 | 47 | meta: [], 48 | link: [], 49 | style: [], 50 | script: [], 51 | noscript: [], 52 | 53 | changed: undefined, 54 | afterNavigation: undefined, 55 | } 56 | } 57 | 58 | type ComputedHead = Array> 59 | 60 | export const getHeadOptions = (options: { head: () => MetaInfo }) => { 61 | const head = function (this: { 62 | head?: MetaInfo | (() => MetaInfo) 63 | _computedHead?: ComputedHead 64 | }) { 65 | const optionHead = 66 | options.head instanceof Function ? options.head.call(this) : options.head 67 | 68 | if (!this._computedHead) return optionHead 69 | 70 | const computedHead = this._computedHead.map(h => { 71 | if (isReactive(h)) return toRaw(h) 72 | if (isRef(h)) return h.value 73 | return h 74 | }) 75 | return defu({} as MetaInfo, ...computedHead.reverse(), optionHead) 76 | } 77 | 78 | return { head } 79 | } 80 | 81 | type ToRefs> = { 82 | [P in keyof T]: Ref 83 | } 84 | 85 | /** 86 | * `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.** 87 | * @example 88 | ```ts 89 | import { defineComponent, useMeta, computed } from '@nuxtjs/composition-api' 90 | 91 | export default defineComponent({ 92 | head: {}, 93 | setup() { 94 | const { title } = useMeta() 95 | title.value = 'My page' 96 | }) 97 | }) 98 | ``` 99 | * @param init Whatever defaults you want to set for `head` properties. 100 | */ 101 | export const useMeta = < 102 | T extends MetaInfo, 103 | MetaRefs extends ToRefs & T> 104 | >( 105 | init?: T | (() => T) 106 | ) => { 107 | const vm = getCurrentInstance() as ReturnType & { 108 | _computedHead?: ComputedHead 109 | _metaRefs?: MetaRefs 110 | } 111 | if (!vm) throw new Error('useMeta must be called within a component.') 112 | 113 | if (!('head' in vm.$options)) 114 | throw new Error( 115 | '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.' 116 | ) 117 | 118 | const refreshMeta = () => vm.$meta().refresh() 119 | 120 | if (!vm._computedHead) { 121 | const metaRefs = reactive(createEmptyMeta()) 122 | vm._computedHead = [metaRefs] 123 | vm._metaRefs = toRefs(metaRefs) as MetaRefs 124 | 125 | if (process.client) { 126 | watch(Object.values(vm._metaRefs), refreshMeta, { immediate: true }) 127 | } 128 | } 129 | 130 | if (init) { 131 | const initRef = init instanceof Function ? computed(init) : ref(init) 132 | vm._computedHead.push(initRef) 133 | 134 | if (process.client) { 135 | watch(initRef, refreshMeta, { immediate: true }) 136 | } 137 | } 138 | 139 | return vm._metaRefs! 140 | } 141 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@nuxtjs/composition-api", 3 | "version": "0.34.0", 4 | "description": "Composition API hooks for Nuxt", 5 | "keywords": [ 6 | "vue", 7 | "vuejs", 8 | "nuxt", 9 | "composition-api", 10 | "typescript", 11 | "javascript" 12 | ], 13 | "repository": "nuxt-community/composition-api", 14 | "license": "MIT", 15 | "author": { 16 | "name": "Daniel Roe", 17 | "email": "daniel@roe.dev", 18 | "url": "https://roe.dev" 19 | }, 20 | "sideEffects": true, 21 | "exports": { 22 | ".": "./dist/runtime/index.mjs", 23 | "./module": "./dist/module/index.mjs", 24 | "./package.json": "./package.json", 25 | "./dist/babel-plugin": "./dist/babel-plugin/index.js", 26 | "./dist/runtime/globals": "./dist/runtime/globals.js", 27 | "./dist/runtime/templates/*": "./dist/runtime/templates/*" 28 | }, 29 | "main": "./dist/runtime/index.js", 30 | "module": "./dist/runtime/index.mjs", 31 | "types": "./dist/runtime/index.d.ts", 32 | "files": [ 33 | "dist", 34 | "module.js" 35 | ], 36 | "scripts": { 37 | "build": "siroc build && cp dist/runtime/index.d.ts dist/runtime/index.d.mts", 38 | "clean:fixture": "rimraf test/fixture/dist/ test/fixture/.nuxt", 39 | "dev": "nuxt dev test/fixture", 40 | "fixture:generate": "yarn fixture:generate:export && yarn http-server -s -p 8000 test/fixture/dist", 41 | "fixture:generate:export": "yarn clean:fixture && cross-env GENERATE=true PORT=6000 CMD=generate yarn nuxt-run", 42 | "fixture:prod": "yarn clean:fixture && cross-env CMD=build yarn nuxt-run && cross-env CMD=start yarn nuxt-run", 43 | "lint": "run-s lint:all:*", 44 | "lint:all:eslint": "yarn lint:eslint --ext .js,.mjs,.ts,.vue .", 45 | "lint:eslint": "eslint --fix", 46 | "now-build": "NOW_BUILD=true yarn fixture:generate:export", 47 | "nuxt-run": "cross-env-shell \"yarn nuxt $CMD test/fixture\"", 48 | "prepare": "yarn build", 49 | "_prepublishOnly": "yarn lint && yarn test", 50 | "release": "release-it", 51 | "test": "run-s test:*", 52 | "test:e2e-generated": "cross-env GENERATE=true PORT=8000 start-server-and-test fixture:generate http://localhost:8000 \"testcafe -q firefox:headless test/e2e\"", 53 | "test:e2e-globals": "cross-env GLOBALS=true PORT=3000 start-server-and-test fixture:prod http://localhost:3000 \"testcafe firefox:headless test/e2e\"", 54 | "test:e2e-ssr": "cross-env PORT=4000 start-server-and-test fixture:prod http://localhost:4000 \"testcafe firefox:headless test/e2e\"", 55 | "test:types": "tsd", 56 | "test:unit": "vitest run", 57 | "watch": "yarn build -w" 58 | }, 59 | "dependencies": { 60 | "defu": "^6.1.4", 61 | "estree-walker": "^2.0.2", 62 | "fs-extra": "^11.2.0", 63 | "magic-string": "^0.30.14", 64 | "pathe": "^1.1.2", 65 | "ufo": "^1.5.4" 66 | }, 67 | "devDependencies": { 68 | "@babel/traverse": "^7.26.4", 69 | "@babel/types": "^7.26.3", 70 | "@nuxt/test-utils": "^0.2.2", 71 | "@nuxt/types": "^2.18.1", 72 | "@nuxt/typescript-build": "^3.0.2", 73 | "@nuxtjs/module-test-utils": "^1.6.3", 74 | "@nuxtjs/pwa": "^3.3.5", 75 | "@release-it/conventional-changelog": "^9.0.3", 76 | "@types/fs-extra": "^11.0.4", 77 | "@types/node": "^22.10.1", 78 | "@types/normalize-path": "^3.0.2", 79 | "@typescript-eslint/eslint-plugin": "^7.18.0", 80 | "@typescript-eslint/parser": "^7.18.0", 81 | "codecov": "^3.8.3", 82 | "core-js": "3.39.0", 83 | "cross-env": "^7.0.3", 84 | "eslint": "^8.57.1", 85 | "eslint-plugin-promise": "^7.2.1", 86 | "happy-dom": "^15.11.7", 87 | "http-server": "^14.1.1", 88 | "lint-staged": "^15.2.10", 89 | "npm-run-all2": "^7.0.1", 90 | "nuxt": "^2.18.1", 91 | "release-it": "17.10.0", 92 | "rimraf": "^6.0.1", 93 | "siroc": "0.16.0", 94 | "start-server-and-test": "^2.0.8", 95 | "testcafe": "3.5.0", 96 | "ts-loader": "^8.4.0", 97 | "tsd": "^0.31.2", 98 | "typescript": "5.7.2", 99 | "vite": "^5.4.11", 100 | "vitest": "^2.1.8", 101 | "yorkie": "^2.0.0" 102 | }, 103 | "peerDependencies": { 104 | "nuxt": "^2.18.1", 105 | "vue": "^2.7.16" 106 | }, 107 | "engines": { 108 | "node": ">=v14.13.0" 109 | }, 110 | "gitHooks": { 111 | "pre-commit": "lint-staged" 112 | }, 113 | "tsd": { 114 | "directory": "test/tsd", 115 | "compilerOptions": { 116 | "rootDir": "." 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/module/index.ts: -------------------------------------------------------------------------------- 1 | import type { Module, NuxtOptions } from '@nuxt/types' 2 | import { resolve } from 'pathe' 3 | 4 | import { name, version } from '../../package.json' 5 | 6 | import { registerBabelPlugin } from './babel-register' 7 | import { addGlobalsFile } from './globals-register' 8 | import { addResolvedTemplate, resolveCoreJsVersion } from './utils' 9 | 10 | const compositionApiModule: Module = function compositionApiModule() { 11 | const nuxt = this.nuxt as { options: NuxtOptions } 12 | 13 | // Register globals file where Nuxt config can be accessed from live library 14 | 15 | addGlobalsFile.call(this) 16 | 17 | // Force transpilation of this library (to enable resolution of globals file) 18 | 19 | const runtimeDir = resolve(__dirname, '../runtime') 20 | nuxt.options.build.transpile = nuxt.options.build.transpile || [] 21 | nuxt.options.build.transpile.push('@nuxtjs/composition-api', runtimeDir) 22 | 23 | // Define vue resolution to prevent VCA being registered to the wrong Vue instance 24 | 25 | const vueEntry = 26 | nuxt.options.alias.vue || 27 | (nuxt.options.dev 28 | ? this.nuxt.resolver.resolveModule('vue/dist/vue.common.dev.js') 29 | : this.nuxt.resolver.resolveModule('vue/dist/vue.runtime.esm.js')) 30 | 31 | const vueAliases = Object.fromEntries( 32 | [ 33 | // vue 2 dist files 34 | '.common.dev', 35 | '.common', 36 | '.common.prod', 37 | '.esm.browser', 38 | '.esm.browser.min', 39 | '.esm', 40 | '', 41 | '.min', 42 | '.runtime.common.dev', 43 | '.runtime.common', 44 | '.runtime.common.prod', 45 | '.runtime.esm', 46 | '.runtime', 47 | '.runtime.min', 48 | ] 49 | .flatMap(m => [`vue/dist/vue${m}`, `vue/dist/vue${m}.js`]) 50 | .map(m => [m, vueEntry]) 51 | ) 52 | 53 | nuxt.options.alias = { 54 | ...vueAliases, 55 | ...nuxt.options.alias, 56 | vue: vueEntry, 57 | } 58 | 59 | // Define @nuxtjs/composition-api resolution to ensure plugins register global context successfully 60 | 61 | nuxt.options.alias['@nuxtjs/composition-api'] = 62 | nuxt.options.alias['@nuxtjs/composition-api'] || 63 | this.nuxt.resolver 64 | .resolveModule('@nuxtjs/composition-api') 65 | .replace('.js', '.mjs') 66 | 67 | // Turn off webpack4 module context for .mjs files (as it appears to have some issues) 68 | 69 | this.extendBuild(config => { 70 | if (!config.module) return 71 | 72 | config.module.rules.forEach(rule => { 73 | if (rule.test instanceof RegExp && rule.test.test('index.mjs')) { 74 | rule.type = 'javascript/auto' 75 | } 76 | }) 77 | 78 | config.module.rules.unshift({ 79 | test: /\.mjs$/, 80 | type: 'javascript/auto', 81 | include: [/node_modules/], 82 | }) 83 | }) 84 | 85 | // If we're using nuxt-vite, register vite plugin for injecting keys 86 | 87 | this.nuxt.hook('vite:extend', async (ctx: any) => { 88 | const { compositionApiPlugin } = await import('./vite-plugin') 89 | ctx.config.plugins.push(compositionApiPlugin()) 90 | }) 91 | 92 | // If we're using Babel, register Babel plugin for injecting keys 93 | 94 | registerBabelPlugin.call(this) 95 | 96 | // Add appropriate corejs polyfill for IE support 97 | 98 | addResolvedTemplate.call(this, 'polyfill.client.mjs', { 99 | corejsPolyfill: resolveCoreJsVersion.call(this), 100 | }) 101 | 102 | // Plugin to allow running onGlobalSetup 103 | const globalPlugin = addResolvedTemplate.call(this, 'plugin.mjs') 104 | 105 | // Allow setting head() within onGlobalSetup 106 | const metaPlugin = addResolvedTemplate.call(this, 'meta.mjs') 107 | 108 | this.nuxt.hook('modules:done', () => { 109 | nuxt.options.plugins.push(metaPlugin) 110 | nuxt.options.plugins.unshift(globalPlugin) 111 | }) 112 | 113 | if (!this.nuxt.options.capi?.disableMigrationWarning) { 114 | this.nuxt.hook('build:done', () => { 115 | console.info( 116 | "`Nuxt Bridge has now been released in beta.` It has full composition API support and it's strongly recommended to migrate from `@nuxtjs/composition-api`, if possible, by following the steps at `https://nuxt.com/docs/bridge/overview`. Feedback welcome at `https://github.com/nuxt-community/composition-api/discussions/585`.\n" 117 | ) 118 | }) 119 | } 120 | } 121 | 122 | // eslint-disable-next-line 123 | // @ts-ignore 124 | compositionApiModule.meta = { 125 | name, 126 | version, 127 | } 128 | 129 | export default compositionApiModule 130 | -------------------------------------------------------------------------------- /src/runtime/composables/static.ts: -------------------------------------------------------------------------------- 1 | import { onServerPrefetch, watch, computed, ref } from 'vue' 2 | import type { Ref } from 'vue' 3 | 4 | import { joinURL } from 'ufo' 5 | 6 | import { ssrRef } from './ssr-ref' 7 | import { 8 | globalContext, 9 | staticPath, 10 | publicPath as _publicPath, 11 | } from '@nuxtjs/composition-api/dist/runtime/globals' 12 | 13 | const staticCache: Record = {} 14 | 15 | function writeFile(key: string, value: string) { 16 | if (process.client || !process.static) return 17 | 18 | const { writeFileSync }: typeof import('fs') = process.client 19 | ? '' 20 | : require('fs') 21 | const { join }: typeof import('pathe') = process.client 22 | ? '' 23 | : require('pathe') 24 | 25 | try { 26 | writeFileSync(join(staticPath, `${key}.json`), value) 27 | } catch (e) { 28 | console.log(e) 29 | } 30 | } 31 | /** 32 | * You can pre-run expensive functions using `useStatic`. 33 | * 34 | * __SSG__ 35 | * 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: 36 | 37 | 1. On generate, the result of a `useStatic` call will be saved to a JSON file and copied into the `/dist` directory. 38 | 2. On hard-reload of a generated page, the JSON will be inlined into the page and cached. 39 | 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. 40 | 41 | 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).) 42 | 43 | * 44 | * __SSR__ 45 | * If the route is not pre-generated (including in dev mode), then: 46 | 47 | 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. 48 | 2. On client navigation, the client will run the factory function. 49 | 50 | 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. 51 | 52 | * @param factory The async function that will populate the ref this function returns. It receives the param and keyBase (see below) as parameters. 53 | * @param param A an optional param (such as an ID) to distinguish multiple API fetches using the same factory function. 54 | * @param keyBase A key that should be unique across your project. If not provided, this will be auto-generated by `@nuxtjs/composition-api`. 55 | * @example 56 | ```ts 57 | import { defineComponent, useContext, useStatic, computed } from '@nuxtjs/composition-api' 58 | import axios from 'axios' 59 | 60 | export default defineComponent({ 61 | setup() { 62 | const { params } = useContext() 63 | const id = computed(() => params.value.id) 64 | const post = useStatic( 65 | id => axios.get(`https://jsonplaceholder.typicode.com/posts/${id}`), 66 | id, 67 | 'post' 68 | ) 69 | 70 | return { post } 71 | }, 72 | }) 73 | ``` 74 | */ 75 | export const useStatic = ( 76 | factory: (param: string, key: string) => Promise, 77 | param: Ref = ref(''), 78 | keyBase: string 79 | ): Ref => { 80 | const key = computed(() => `${keyBase}-${param.value}`) 81 | const result = ssrRef(null, key.value) 82 | 83 | if (result.value) staticCache[key.value] = result.value 84 | 85 | if (process.client) { 86 | const publicPath = 87 | (window as any)[globalContext].$config?.app?.cdnURL || _publicPath 88 | const onFailure = () => 89 | factory(param.value, key.value).then(r => { 90 | staticCache[key.value] = r 91 | result.value = r 92 | return 93 | }) 94 | watch( 95 | key, 96 | key => { 97 | if (key in staticCache) { 98 | result.value = staticCache[key] 99 | return 100 | } 101 | /* eslint-disable promise/always-return */ 102 | if (!process.static) onFailure() 103 | else 104 | fetch(joinURL(publicPath, `${key}.json`)) 105 | .then(response => { 106 | if (!response.ok) throw new Error('Response invalid.') 107 | return response.json() 108 | }) 109 | .then(json => { 110 | staticCache[key] = json 111 | result.value = json 112 | }) 113 | .catch(onFailure) 114 | /* eslint-enable */ 115 | }, 116 | { 117 | immediate: true, 118 | flush: 'post', 119 | } 120 | ) 121 | } else { 122 | if (key.value in staticCache) { 123 | result.value = staticCache[key.value] 124 | return result as Ref 125 | } 126 | onServerPrefetch(async () => { 127 | const [_key, _param] = [key.value, param.value] 128 | 129 | result.value = await factory(_param, _key) 130 | staticCache[_key] = result.value 131 | writeFile(_key, JSON.stringify(result.value)) 132 | }) 133 | } 134 | 135 | return result as Ref 136 | } 137 | -------------------------------------------------------------------------------- /src/runtime/composables/ssr-ref.ts: -------------------------------------------------------------------------------- 1 | import { customRef, onServerPrefetch, ref, shallowRef } from 'vue' 2 | import type { Ref } from 'vue' 3 | 4 | import { 5 | globalContext, 6 | globalNuxt, 7 | } from '@nuxtjs/composition-api/dist/runtime/globals' 8 | import { getCurrentInstance, validateKey } from './utils' 9 | 10 | function getValue(value: T | (() => T)): T { 11 | if (value instanceof Function) return value() 12 | return value 13 | } 14 | 15 | let globalRefs: any = {} 16 | 17 | export function setSSRContext(app: any) { 18 | globalRefs = Object.assign({}, {}) 19 | app.context.ssrContext.nuxt.globalRefs = globalRefs 20 | } 21 | 22 | const useServerData = () => { 23 | const vm = getCurrentInstance() 24 | const type: 'globalRefs' | 'ssrRefs' = vm ? 'ssrRefs' : 'globalRefs' 25 | 26 | let ssrRefs: Record 27 | 28 | if (vm && process.server) { 29 | const ssrContext = (vm![globalNuxt] || vm!.$options).context.ssrContext! 30 | ssrRefs = (ssrContext.nuxt as any).ssrRefs = 31 | (ssrContext.nuxt as any).ssrRefs || {} 32 | } 33 | 34 | const setData = (key: string, val: any) => { 35 | const refs = ssrRefs || globalRefs 36 | refs[key] = sanitise(val) 37 | } 38 | 39 | return { type, setData } 40 | } 41 | 42 | const isProxyable = (val: unknown): val is Record => 43 | !!val && typeof val === 'object' 44 | 45 | export const sanitise = (val: unknown) => 46 | (val && JSON.parse(JSON.stringify(val))) || val 47 | 48 | const ssrValue = ( 49 | value: T | (() => T), 50 | key: string, 51 | type: 'globalRefs' | 'ssrRefs' = 'globalRefs' 52 | ): T => { 53 | if (process.client) { 54 | if ( 55 | process.env.NODE_ENV === 'development' && 56 | window[globalNuxt]?.context.isHMR 57 | ) { 58 | return getValue(value) 59 | } 60 | return (window as any)[globalContext]?.[type]?.[key] ?? getValue(value) 61 | } 62 | return getValue(value) 63 | } 64 | 65 | /** 66 | * `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.** 67 | * @param value This can be an initial value or a factory function that will be executed on server-side to get the initial value. 68 | * @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/module` 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/dist/babel-plugin` to your Babel plugins. 69 | * @example 70 | ```ts 71 | import { ssrRef } from '@nuxtjs/composition-api' 72 | 73 | const val = ssrRef('') 74 | 75 | // When hard-reloaded, `val` will be initialised to 'server set' 76 | if (process.server) val.value = 'server set' 77 | 78 | // When hard-reloaded, the result of myExpensiveSetterFunction() will 79 | // be encoded in nuxtState and used as the initial value of this ref. 80 | // If client-loaded, the setter function will run to come up with initial value. 81 | const val2 = ssrRef(myExpensiveSetterFunction) 82 | ``` 83 | */ 84 | export const ssrRef = (value: T | (() => T), key?: string): Ref => { 85 | validateKey(key) 86 | 87 | const { type, setData } = useServerData() 88 | 89 | let val = ssrValue(value, key, type) 90 | 91 | if (process.client) return ref(val) as Ref 92 | 93 | if (value instanceof Function) setData(key, val) 94 | 95 | const getProxy = >( 96 | track: () => void, 97 | trigger: () => void, 98 | observable: T 99 | ): T => 100 | new Proxy(observable, { 101 | get(target, prop: string) { 102 | track() 103 | if (isProxyable(target[prop])) 104 | return getProxy(track, trigger, target[prop]) 105 | 106 | const value = Reflect.get(target, prop) 107 | 108 | return typeof value === 'function' ? value.bind(target) : value 109 | }, 110 | set(obj, prop, newVal) { 111 | const result = Reflect.set(obj, prop, newVal) 112 | setData(key, val) 113 | trigger() 114 | return result 115 | }, 116 | }) 117 | 118 | const proxy = customRef((track, trigger) => ({ 119 | get: () => { 120 | track() 121 | if (isProxyable(val)) return getProxy(track, trigger, val) 122 | return val 123 | }, 124 | set: (v: T) => { 125 | setData(key, v) 126 | val = v 127 | trigger() 128 | }, 129 | })) 130 | 131 | return proxy 132 | } 133 | 134 | /** 135 | * 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. 136 | * @param value This can be an initial value or a factory function that will be executed on server-side to get the initial value. 137 | * @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/module` 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/dist/babel-plugin` to your Babel plugins. 138 | 139 | * @example 140 | ```ts 141 | import { shallowSsrRef, onMounted } from '@nuxtjs/composition-api' 142 | 143 | const shallow = shallowSsrRef({ v: 'init' }) 144 | if (process.server) shallow.value = { v: 'changed' } 145 | 146 | // On client-side, shallow.value will be { v: changed } 147 | onMounted(() => { 148 | // This and other changes outside of setup won't trigger component updates. 149 | shallow.value.v = 'Hello World' 150 | }) 151 | ``` 152 | */ 153 | export const shallowSsrRef = ( 154 | value: T | (() => T), 155 | key?: string 156 | ): Ref => { 157 | validateKey(key) 158 | const { type, setData } = useServerData() 159 | 160 | if (process.client) return shallowRef(ssrValue(value, key, type)) 161 | 162 | const _val = getValue(value) 163 | 164 | if (value instanceof Function) { 165 | setData(key, _val) 166 | } 167 | 168 | return customRef((track, trigger) => ({ 169 | get() { 170 | track() 171 | return _val 172 | }, 173 | set(newValue: T) { 174 | setData(key, newValue) 175 | value = newValue 176 | trigger() 177 | }, 178 | })) 179 | } 180 | 181 | /** 182 | * `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.) 183 | * @param value This can be an initial value or a factory function that will be executed on server-side to get the initial value. 184 | * @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/module` 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/dist/babel-plugin` to your Babel plugins. 185 | * @example 186 | 187 | ```ts 188 | import { 189 | defineComponent, 190 | onBeforeMount, 191 | ref, 192 | ssrPromise, 193 | } from '@nuxtjs/composition-api' 194 | 195 | export default defineComponent({ 196 | setup() { 197 | const _promise = ssrPromise(async () => myAsyncFunction()) 198 | const resolvedPromise = ref(null) 199 | 200 | onBeforeMount(async () => { 201 | resolvedPromise.value = await _promise 202 | }) 203 | 204 | return { 205 | // On the server, this will be null until the promise resolves. 206 | // On the client, if server-rendered, this will always be the resolved promise. 207 | resolvedPromise, 208 | } 209 | }, 210 | }) 211 | ``` 212 | */ 213 | export const ssrPromise = ( 214 | value: () => Promise, 215 | key?: string 216 | ): Promise => { 217 | validateKey(key) 218 | const { type, setData } = useServerData() 219 | 220 | const val = ssrValue(value, key, type) 221 | if (process.client) return Promise.resolve(val) 222 | 223 | onServerPrefetch(async () => { 224 | setData(key, await val) 225 | }) 226 | return val 227 | } 228 | -------------------------------------------------------------------------------- /src/runtime/composables/fetch.ts: -------------------------------------------------------------------------------- 1 | import { 2 | isRef, 3 | isReactive, 4 | onBeforeMount, 5 | onServerPrefetch, 6 | reactive, 7 | ComponentInstance, 8 | set, 9 | } from 'vue' 10 | 11 | import { 12 | globalContext, 13 | globalNuxt, 14 | isFullStatic, 15 | } from '@nuxtjs/composition-api/dist/runtime/globals' 16 | import type { NuxtApp } from '@nuxt/types/app' 17 | 18 | import { getCurrentInstance } from './utils' 19 | 20 | const nuxtState = process.client && (window as any)[globalContext] 21 | 22 | function normalizeError(err: any) { 23 | let message: string 24 | if (!(err.message || typeof err === 'string')) { 25 | try { 26 | message = JSON.stringify(err, null, 2) 27 | } catch (e) { 28 | message = `[${err.constructor.name}]` 29 | } 30 | } else { 31 | message = err.message || err 32 | } 33 | return { 34 | ...err, 35 | message, 36 | statusCode: 37 | err.statusCode || 38 | err.status || 39 | (err.response && err.response.status) || 40 | 500, 41 | } 42 | } 43 | 44 | function createGetCounter(counterObject: Record, defaultKey = '') { 45 | return function getCounter(id = defaultKey) { 46 | if (counterObject[id] === undefined) { 47 | counterObject[id] = 0 48 | } 49 | return counterObject[id]++ 50 | } 51 | } 52 | 53 | interface Fetch { 54 | (context: ComponentInstance): void | Promise 55 | } 56 | 57 | const fetches = new WeakMap() 58 | const fetchPromises = new Map>() 59 | 60 | const isSsrHydration = (vm: ComponentInstance) => 61 | (vm.$vnode?.elm as any)?.dataset?.fetchKey 62 | 63 | interface AugmentedComponentInstance extends ComponentInstance { 64 | _fetchKey?: number | string 65 | _data?: any 66 | _hydrated?: boolean 67 | _fetchDelay?: number 68 | _fetchOnServer?: boolean 69 | } 70 | 71 | interface AugmentedNuxtApp extends NuxtApp { 72 | isPreview?: boolean 73 | _payloadFetchIndex?: number 74 | _pagePayload?: any 75 | } 76 | 77 | function registerCallback(vm: ComponentInstance, callback: Fetch) { 78 | const callbacks = fetches.get(vm) || [] 79 | fetches.set(vm, [...callbacks, callback]) 80 | } 81 | 82 | async function callFetches(this: AugmentedComponentInstance) { 83 | const fetchesToCall = fetches.get(this) 84 | if (!fetchesToCall) return 85 | ;(this[globalNuxt] as any).nbFetching++ 86 | 87 | this.$fetchState.pending = true 88 | this.$fetchState.error = null 89 | this._hydrated = false 90 | 91 | let error = null 92 | const startTime = Date.now() 93 | 94 | try { 95 | await Promise.all( 96 | fetchesToCall.map(fetch => { 97 | if (fetchPromises.has(fetch)) return fetchPromises.get(fetch) 98 | const promise = Promise.resolve(fetch(this)).finally(() => 99 | fetchPromises.delete(fetch) 100 | ) 101 | fetchPromises.set(fetch, promise) 102 | return promise 103 | }) 104 | ) 105 | } catch (err) { 106 | if ((process as any).dev) { 107 | console.error('Error in fetch():', err) 108 | } 109 | error = normalizeError(err) 110 | } 111 | 112 | const delayLeft = (this._fetchDelay || 0) - (Date.now() - startTime) 113 | if (delayLeft > 0) { 114 | await new Promise(resolve => setTimeout(resolve, delayLeft)) 115 | } 116 | 117 | this.$fetchState.error = error 118 | this.$fetchState.pending = false 119 | this.$fetchState.timestamp = Date.now() 120 | 121 | this.$nextTick(() => (this[globalNuxt] as any).nbFetching--) 122 | } 123 | 124 | const setFetchState = (vm: AugmentedComponentInstance) => { 125 | vm.$fetchState = 126 | vm.$fetchState || 127 | reactive({ 128 | error: null, 129 | pending: false, 130 | timestamp: 0, 131 | }) 132 | } 133 | 134 | const mergeDataOnMount = (data: Record) => { 135 | const vm = getCurrentInstance() as AugmentedComponentInstance | undefined 136 | if (!vm) throw new Error('This must be called within a setup function.') 137 | 138 | onBeforeMount(() => { 139 | const vmData = (vm as any)._setupProxy || vm 140 | // Merge data 141 | for (const key in data) { 142 | try { 143 | // Assign missing properties 144 | if (key in vmData) { 145 | const _key = key as keyof typeof vmData 146 | // Skip value equal to incoming data 147 | if (vmData[_key] === data[key]) continue 148 | // Skip functions (not stringifiable) 149 | if (typeof vmData[_key] === 'function') continue 150 | // Preserve reactive objects 151 | if (isReactive(vmData[_key])) { 152 | // Unset keys that do not exist in incoming data 153 | for (const k in vmData[_key]) { 154 | if (!(k in data[key])) { 155 | delete vmData[_key][k] 156 | } 157 | } 158 | Object.assign(vmData[_key], data[key]) 159 | continue 160 | } 161 | } 162 | set(vmData, key, data[key]) 163 | } catch (e) { 164 | if (process.env.NODE_ENV === 'development') 165 | // eslint-disable-next-line 166 | console.warn(`Could not hydrate ${key}.`) 167 | } 168 | } 169 | }) 170 | } 171 | 172 | const loadFullStatic = (vm: AugmentedComponentInstance) => { 173 | vm._fetchKey = getKey(vm) 174 | // Check if component has been fetched on server 175 | const { fetchOnServer } = vm.$options 176 | const fetchedOnServer = 177 | typeof fetchOnServer === 'function' 178 | ? fetchOnServer.call(vm) !== false 179 | : fetchOnServer !== false 180 | 181 | const nuxt = vm[globalNuxt] as AugmentedNuxtApp 182 | if (!fetchedOnServer || nuxt?.isPreview || !nuxt?._pagePayload) { 183 | return 184 | } 185 | vm._hydrated = true 186 | const data = nuxt._pagePayload.fetch[vm._fetchKey!] 187 | 188 | // If fetch error 189 | if (data && data._error) { 190 | vm.$fetchState.error = data._error 191 | return 192 | } 193 | 194 | mergeDataOnMount(data) 195 | } 196 | 197 | async function serverPrefetch(vm: AugmentedComponentInstance) { 198 | if (!vm._fetchOnServer) return 199 | 200 | // Call and await on $fetch 201 | setFetchState(vm) 202 | 203 | try { 204 | await callFetches.call(vm) 205 | } catch (err) { 206 | if ((process as any).dev) { 207 | console.error('Error in fetch():', err) 208 | } 209 | vm.$fetchState.error = normalizeError(err) 210 | } 211 | vm.$fetchState.pending = false 212 | 213 | // Define an ssrKey for hydration 214 | vm._fetchKey = 215 | // Nuxt 2.15+ uses a different format - an object rather than an array 216 | 'push' in vm.$ssrContext.nuxt.fetch 217 | ? vm.$ssrContext.nuxt.fetch.length 218 | : vm._fetchKey || vm.$ssrContext.fetchCounters['']++ 219 | 220 | // Add data-fetch-key on parent element of Component 221 | if (!vm.$vnode.data) vm.$vnode.data = {} 222 | const attrs = (vm.$vnode.data.attrs = vm.$vnode.data.attrs || {}) 223 | attrs['data-fetch-key'] = vm._fetchKey 224 | const data = Object.fromEntries( 225 | Object.entries((vm as any)?._setupProxy || (vm as any)?._setupState) 226 | .filter( 227 | // eslint-disable-next-line 228 | ([_key, val]) => 229 | !( 230 | (val && typeof val === 'object' && '_compiled' in val) || 231 | val instanceof Function || 232 | val instanceof Promise 233 | ) 234 | ) 235 | .map(([key, val]) => [key, isRef(val) ? val.value : val]) 236 | ) 237 | 238 | // Add to ssrContext for window.__NUXT__.fetch 239 | const content = vm.$fetchState.error 240 | ? { _error: vm.$fetchState.error } 241 | : JSON.parse(JSON.stringify(data)) 242 | if ('push' in vm.$ssrContext.nuxt.fetch) { 243 | vm.$ssrContext.nuxt.fetch.push(content) 244 | } else { 245 | vm.$ssrContext.nuxt.fetch[vm._fetchKey!] = content 246 | } 247 | } 248 | 249 | function getKey(vm: AugmentedComponentInstance) { 250 | const nuxtState = vm[globalNuxt] as any 251 | if (process.server && 'push' in vm.$ssrContext.nuxt.fetch) { 252 | return undefined 253 | } else if (process.client && '_payloadFetchIndex' in nuxtState) { 254 | nuxtState._payloadFetchIndex = nuxtState._payloadFetchIndex || 0 255 | return nuxtState._payloadFetchIndex++ 256 | } 257 | const defaultKey = (vm.$options as any)._scopeId || vm.$options.name || '' 258 | const getCounter = createGetCounter( 259 | process.server 260 | ? vm.$ssrContext.fetchCounters 261 | : (vm[globalNuxt] as any)._fetchCounters, 262 | defaultKey 263 | ) 264 | 265 | const options: { 266 | fetchKey: 267 | | ((getCounter: ReturnType) => string) 268 | | string 269 | } = vm.$options as any 270 | 271 | if (typeof options.fetchKey === 'function') { 272 | return options.fetchKey.call(vm, getCounter) 273 | } else { 274 | const key = 275 | 'string' === typeof options.fetchKey ? options.fetchKey : defaultKey 276 | return key ? key + ':' + getCounter(key) : String(getCounter(key)) 277 | } 278 | } 279 | 280 | /** 281 | * 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. 282 | 283 | * @param callback The async function you want to run. 284 | * @example 285 | 286 | ```ts 287 | import { defineComponent, ref, useFetch } from '@nuxtjs/composition-api' 288 | import axios from 'axios' 289 | 290 | export default defineComponent({ 291 | setup() { 292 | const name = ref('') 293 | 294 | const { fetch, fetchState } = useFetch(async () => { 295 | name.value = await axios.get('https://myapi.com/name') 296 | }) 297 | 298 | // Manually trigger a refetch 299 | fetch() 300 | 301 | // Access fetch error, pending and timestamp 302 | fetchState 303 | 304 | return { name } 305 | }, 306 | }) 307 | ``` 308 | */ 309 | export const useFetch = (callback: Fetch) => { 310 | const vm = getCurrentInstance() as AugmentedComponentInstance | undefined 311 | if (!vm) throw new Error('This must be called within a setup function.') 312 | 313 | registerCallback(vm, callback) 314 | 315 | if (typeof vm.$options.fetchOnServer === 'function') { 316 | vm._fetchOnServer = vm.$options.fetchOnServer.call(vm) !== false 317 | } else { 318 | vm._fetchOnServer = vm.$options.fetchOnServer !== false 319 | } 320 | 321 | if (process.server) { 322 | vm._fetchKey = getKey(vm) 323 | } 324 | 325 | setFetchState(vm) 326 | 327 | onServerPrefetch(() => serverPrefetch(vm)) 328 | 329 | function result() { 330 | return { 331 | fetch: vm!.$fetch, 332 | fetchState: vm!.$fetchState, 333 | $fetch: vm!.$fetch, 334 | $fetchState: vm!.$fetchState, 335 | } 336 | } 337 | 338 | vm._fetchDelay = 339 | typeof vm.$options.fetchDelay === 'number' ? vm.$options.fetchDelay : 0 340 | 341 | vm.$fetch = callFetches.bind(vm) 342 | 343 | onBeforeMount(() => !vm._hydrated && callFetches.call(vm)) 344 | 345 | if (process.server || !isSsrHydration(vm)) { 346 | if (process.client && isFullStatic) loadFullStatic(vm) 347 | return result() 348 | } 349 | 350 | // Hydrate component 351 | vm._hydrated = true 352 | vm._fetchKey = (vm.$vnode.elm as any)?.dataset.fetchKey || getKey(vm) 353 | const data = nuxtState.fetch[vm._fetchKey!] 354 | 355 | // If fetch error 356 | if (data && data._error) { 357 | vm.$fetchState.error = data._error 358 | return result() 359 | } 360 | 361 | mergeDataOnMount(data) 362 | 363 | return result() 364 | } 365 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## [0.34.0](https://github.com/nuxt-community/composition-api/compare/0.33.1...0.34.0) (2024-04-12) 4 | 5 | 6 | ### Bug Fixes 7 | 8 | * allow returning of falsy values from useAsync ([#670](https://github.com/nuxt-community/composition-api/issues/670)) ([f8d9128](https://github.com/nuxt-community/composition-api/commit/f8d9128bbced69de3821e7ddc74add4e29962b68)) 9 | * include `.d.mts` file for bundler compatibility ([#786](https://github.com/nuxt-community/composition-api/issues/786)) ([fe769c7](https://github.com/nuxt-community/composition-api/commit/fe769c707496b2f07125cc4a532278df4c277e39)) 10 | * remove `@nuxt/vue-app` peer dependency ([#753](https://github.com/nuxt-community/composition-api/issues/753)) ([f3b8fbf](https://github.com/nuxt-community/composition-api/commit/f3b8fbf40f507e3728b775f05e0ea3cef5f542ad)) 11 | * update link to Bridge documentation ([1571863](https://github.com/nuxt-community/composition-api/commit/1571863e766ab283f9e5c582accc5acc274c0c52)) 12 | 13 | ### [0.33.1](https://github.com/nuxt-community/composition-api/compare/0.33.0...0.33.1) (2022-08-01) 14 | 15 | 16 | ### Bug Fixes 17 | 18 | * lower node engine requirement ([3b91919](https://github.com/nuxt-community/composition-api/commit/3b919195f89b0a591d6c6ac5698c1b397fbde697)), closes [#662](https://github.com/nuxt-community/composition-api/issues/662) 19 | 20 | ## [0.33.0](https://github.com/nuxt-community/composition-api/compare/0.32.0...0.33.0) (2022-07-08) 21 | 22 | 23 | ### ⚠ BREAKING CHANGES 24 | 25 | * upgrade `defu` and other dev-dependencies 26 | * Some of the API and behaviour differ between Vue 2.7 and `@vue/composition-api`. In addition, there are some composition utilites that are no longer exported from `@nuxtjs/composition-api`, such as `defineAsyncComponent`, `warn`, etc. 27 | 28 | Please read https://blog.vuejs.org/posts/vue-2-7-naruto.html for the announcement and more information. 29 | 30 | ### Features 31 | 32 | * use vue 2.7 composition api ([#645](https://github.com/nuxt-community/composition-api/issues/645)) ([ae9307a](https://github.com/nuxt-community/composition-api/commit/ae9307aec6b8f0b5c333f39238ea7339aa8fa411)), closes [#644](https://github.com/nuxt-community/composition-api/issues/644) [#643](https://github.com/nuxt-community/composition-api/issues/643) [#593](https://github.com/nuxt-community/composition-api/issues/593) 33 | 34 | 35 | ### Miscellaneous Chores 36 | 37 | * upgrade `defu` and other dev-dependencies ([1cbce73](https://github.com/nuxt-community/composition-api/commit/1cbce731e807fe6331f6c0f701206806f7ccac7d)) 38 | 39 | ## [0.32.0](https://github.com/nuxt-community/composition-api/compare/0.31.0...0.32.0) (2022-02-22) 40 | 41 | ## [0.31.0](https://github.com/nuxt-community/composition-api/compare/0.30.0...0.31.0) (2021-11-30) 42 | 43 | ## [0.30.0](https://github.com/nuxt-community/composition-api/compare/0.29.3...0.30.0) (2021-11-05) 44 | 45 | 46 | ### Bug Fixes 47 | 48 | * resolve all aliases ([#578](https://github.com/nuxt-community/composition-api/issues/578)) ([d2c7516](https://github.com/nuxt-community/composition-api/commit/d2c7516ebb6478d0628e85c6f2201f86cd7b1abf)) 49 | * use `.mjs` version of `@vue/compoisition-api` ([#577](https://github.com/nuxt-community/composition-api/issues/577)) ([f49884b](https://github.com/nuxt-community/composition-api/commit/f49884b6dbe34869de88b56c2c6ef01d609d8556)) 50 | 51 | ### [0.29.3](https://github.com/nuxt-community/composition-api/compare/0.29.2...0.29.3) (2021-10-11) 52 | 53 | 54 | ### Bug Fixes 55 | 56 | * correctly proxify functions within ssrRef ([#561](https://github.com/nuxt-community/composition-api/issues/561)) ([016ff0a](https://github.com/nuxt-community/composition-api/commit/016ff0aaf115af9208969a4d12becfbdde307204)) 57 | * skip setting values that are unchanged in useFetch ([#552](https://github.com/nuxt-community/composition-api/issues/552)) ([484b061](https://github.com/nuxt-community/composition-api/commit/484b061162549a75da37a7ffe833affb03e285c3)) 58 | 59 | ### [0.29.2](https://github.com/nuxt-community/composition-api/compare/0.29.1...0.29.2) (2021-09-21) 60 | 61 | 62 | ### Features 63 | 64 | * add new exports from `@vue/composition-api` ([6d0ebd0](https://github.com/nuxt-community/composition-api/commit/6d0ebd09af7dbf12a4a8cf87d7aedd5cdfcdedb9)) 65 | 66 | ### [0.29.1](https://github.com/nuxt-community/composition-api/compare/0.29.0...0.29.1) (2021-09-21) 67 | 68 | ## [0.29.0](https://github.com/nuxt-community/composition-api/compare/0.28.0...0.29.0) (2021-09-08) 69 | 70 | ## [0.28.0](https://github.com/nuxt-community/composition-api/compare/0.27.0...0.28.0) (2021-09-04) 71 | 72 | 73 | ### Features 74 | 75 | * enable `script setup` syntax ([#542](https://github.com/nuxt-community/composition-api/issues/542)) ([8bfadeb](https://github.com/nuxt-community/composition-api/commit/8bfadeb9c7355e7fa8d4ac0892f3259570a380b4)) 76 | 77 | 78 | ### Bug Fixes 79 | 80 | * preserve reactive objects on `useFetch` hydration ([#541](https://github.com/nuxt-community/composition-api/issues/541)) ([4c3e734](https://github.com/nuxt-community/composition-api/commit/4c3e73426c4bc78127bca5393b05be4fec65f369)) 81 | 82 | ## [0.27.0](https://github.com/nuxt-community/composition-api/compare/0.26.0...0.27.0) (2021-08-20) 83 | 84 | ## [0.26.0](https://github.com/nuxt-community/composition-api/compare/0.25.2...0.26.0) (2021-07-28) 85 | 86 | 87 | ### Bug Fixes 88 | 89 | * resolve to single vue instance ([#525](https://github.com/nuxt-community/composition-api/issues/525)) ([33171bf](https://github.com/nuxt-community/composition-api/commit/33171bfd4966ad18532c4e343571f567b9f1d31b)) 90 | 91 | ### [0.25.2](https://github.com/nuxt-community/composition-api/compare/0.25.1...0.25.2) (2021-07-27) 92 | 93 | 94 | ### Bug Fixes 95 | 96 | * allow setting ssrRef within `onGlobalSetup` ([#523](https://github.com/nuxt-community/composition-api/issues/523)) ([320c409](https://github.com/nuxt-community/composition-api/commit/320c4098a5e0b071ed464b65aed1851e763755f7)) 97 | 98 | ### [0.25.1](https://github.com/nuxt-community/composition-api/compare/0.25.0...0.25.1) (2021-07-22) 99 | 100 | ## [0.25.0](https://github.com/nuxt-community/composition-api/compare/0.24.7...0.25.0) (2021-07-21) 101 | 102 | 103 | ### Bug Fixes 104 | 105 | * `@vue/composition-api` submodule resolution ([#517](https://github.com/nuxt-community/composition-api/issues/517)) ([bb76b53](https://github.com/nuxt-community/composition-api/commit/bb76b5379a1362e8fcb90ea40340f8aeef3c057a)) 106 | 107 | ### [0.24.7](https://github.com/nuxt-community/composition-api/compare/0.24.6...0.24.7) (2021-07-12) 108 | 109 | ### [0.24.6](https://github.com/nuxt-community/composition-api/compare/0.24.5...0.24.6) (2021-07-02) 110 | 111 | ### [0.24.5](https://github.com/nuxt-community/composition-api/compare/0.24.4...0.24.5) (2021-06-24) 112 | 113 | ### [0.24.4](https://github.com/nuxt-community/composition-api/compare/0.24.3...0.24.4) (2021-06-07) 114 | 115 | ### [0.24.3](https://github.com/nuxt-community/composition-api/compare/0.24.2...0.24.3) (2021-06-02) 116 | 117 | 118 | ### Bug Fixes 119 | 120 | * use `webpack:config` to add entry for better nuxt/storybook compatibility ([#495](https://github.com/nuxt-community/composition-api/issues/495)) ([1b07abe](https://github.com/nuxt-community/composition-api/commit/1b07abeada072da79c1f0105d73389b799f4f8e1)), closes [#494](https://github.com/nuxt-community/composition-api/issues/494) 121 | 122 | ### [0.24.2](https://github.com/nuxt-community/composition-api/compare/list...0.24.2) (2021-05-28) 123 | 124 | ### [0.24.1](https://github.com/nuxt-community/composition-api/compare/0.24.0...0.24.1) (2021-05-28) 125 | 126 | 127 | ### Bug Fixes 128 | 129 | * alias `@nuxtjs/composition-api` to esm library ([5d30325](https://github.com/nuxt-community/composition-api/commit/5d303252fc45da6add8827d745e0ca69d3c8f8c5)), closes [#488](https://github.com/nuxt-community/composition-api/issues/488) 130 | 131 | ## [0.24.0](https://github.com/nuxt-community/composition-api/compare/0.23.4...0.24.0) (2021-05-21) 132 | 133 | 134 | ### ⚠ BREAKING CHANGES 135 | 136 | * package exports have been moved 137 | 138 | * see https://github.com/nuxt-community/composition-api/pull/480 for context and further changes to come 139 | * output `.mjs` files for runtime templates 140 | * (outside of Nuxt) only auto-registers plugin if NODE_ENV == 'test' 141 | 142 | ### Bug Fixes 143 | 144 | * add meta plugin after other modules ([#481](https://github.com/nuxt-community/composition-api/issues/481)) ([86ed34e](https://github.com/nuxt-community/composition-api/commit/86ed34ee95640fc9c1039f5ec70c60d7797275fa)) 145 | * rework composition API registration ([#484](https://github.com/nuxt-community/composition-api/issues/484)) ([1e423c3](https://github.com/nuxt-community/composition-api/commit/1e423c3783ba2aa70b38a536366623cf8b181515)), closes [#476](https://github.com/nuxt-community/composition-api/issues/476) [#479](https://github.com/nuxt-community/composition-api/issues/479) 146 | 147 | ### Build System 148 | 149 | * move `globals` and `register` to runtime directory ([842a724](https://github.com/nuxt-community/composition-api/commit/842a724ccaa26e6e7812f6d6ac7cce4ef49de506)) 150 | * output `.mjs` files for runtime templates ([23afd5a](https://github.com/nuxt-community/composition-api/commit/23afd5acb00326753da23dea3583bc08fb086e29)) 151 | 152 | ### [0.23.4](https://github.com/nuxt-community/composition-api/compare/0.23.3...0.23.4) (2021-04-28) 153 | 154 | ### [0.23.3](https://github.com/nuxt-community/composition-api/compare/0.23.2...0.23.3) (2021-04-24) 155 | 156 | 157 | ### Bug Fixes 158 | 159 | * alias module so we can access `process.server` in vite node build ([#462](https://github.com/nuxt-community/composition-api/issues/462)) ([7c997c5](https://github.com/nuxt-community/composition-api/commit/7c997c5701e9d5b23af313de44df0d95655daa6a)) 160 | * correctly generate vite sourcemap ([cf4f438](https://github.com/nuxt-community/composition-api/commit/cf4f4389b0955874cfe87360326c484b82abb50b)) 161 | * export default from register entry ([#461](https://github.com/nuxt-community/composition-api/issues/461)) ([e491722](https://github.com/nuxt-community/composition-api/commit/e491722656a548ffe262aab49bd4471a5318a587)) 162 | 163 | ### [0.23.2](https://github.com/nuxt-community/composition-api/compare/0.23.1...0.23.2) (2021-04-19) 164 | 165 | 166 | ### Bug Fixes 167 | 168 | * register composition api before middleware ([9d4165a](https://github.com/nuxt-community/composition-api/commit/9d4165a3511ffeb2cf1e0584a303565b41b0f968)), closes [#457](https://github.com/nuxt-community/composition-api/issues/457) 169 | 170 | ### [0.23.1](https://github.com/nuxt-community/composition-api/compare/0.23.0...0.23.1) (2021-04-14) 171 | 172 | 173 | ### Bug Fixes 174 | 175 | * use reactive rather than `Vue.observable` in `useFetch` ([385bb73](https://github.com/nuxt-community/composition-api/commit/385bb73bab6cfbc06e4567fe3e314d1b6888f830)), closes [#455](https://github.com/nuxt-community/composition-api/issues/455) 176 | 177 | ## [0.23.0](https://github.com/nuxt-community/composition-api/compare/0.22.4...0.23.0) (2021-04-12) 178 | 179 | 180 | ### ⚠ BREAKING CHANGES 181 | 182 | * `@nuxtjs/composition-api/module` is now how the module should be imported in `nuxt.config` 183 | 184 | * There is no longer a need to amend `moduleNameMapper` in your jest.config - you should remove any entry for `@nuxtjs/composition-api` there. When used outside a Nuxt context, the module will 'auto-mock' and use https://github.com/nuxt-community/composition-api/blob/be94d4f4e1321565864dd3d3d5e850f7cabf6ca9/src/globals.ts instead of live Nuxt configuration. 185 | 186 | * `@nuxtjs/composition-api/babel` has been renamed to `@nuxtjs/composition-api/dist/babel-plugin`. 187 | 188 | * `@vue/composition-api` is no longer transpiled by default. You should consider adding it to `build.transpile`, but note that you may also need to add any other libraries that also use `@vue/composition-api`. 189 | 190 | * You should ensure you haven't named any layout '0' as this will conflict with how this package is registering the Vue Composition API. 191 | 192 | * `defineNuxtConfig`, `defineNuxtModule` and `defineNuxtServerMiddleware` have been removed. You can create your own helper with the following code: 193 | ```ts 194 | import { Module, ServerMiddleware, NuxtConfig } from '@nuxt/types' 195 | 196 | export const defineNuxtModule = >(module: Module) => module 197 | export const defineNuxtServerMiddleware = (serverMiddleware: ServerMiddleware) => serverMiddleware 198 | export const defineNuxtConfig = (config: NuxtConfig) => config 199 | ``` 200 | 201 | ### Bug Fixes 202 | 203 | * register the composition api in a template file ([#433](https://github.com/nuxt-community/composition-api/issues/433)) ([8cc21ce](https://github.com/nuxt-community/composition-api/commit/8cc21ce2de08fbb12d0561c9bbd5c76ecfa32e08)), closes [#415](https://github.com/nuxt-community/composition-api/issues/415) [#391](https://github.com/nuxt-community/composition-api/issues/391) 204 | * return `from` rather than `redirectedFrom` from `useContext` ([a159d87](https://github.com/nuxt-community/composition-api/commit/a159d8781a0eef9071af05bbf1092061b8b653e2)), closes [#444](https://github.com/nuxt-community/composition-api/issues/444) 205 | 206 | 207 | ### Code Refactoring 208 | 209 | * change how library is imported ([#418](https://github.com/nuxt-community/composition-api/issues/418)) ([5bb1a72](https://github.com/nuxt-community/composition-api/commit/5bb1a72669f97ba964dd3968f76627472c502dca)) 210 | - fixed an issue with `nuxt-vite` compatbility 211 | * significant packaging changes ([#438](https://github.com/nuxt-community/composition-api/issues/438)) ([ddc9c0f](https://github.com/nuxt-community/composition-api/commit/ddc9c0f9ba8281931da817b3d106d0a45a2b4cf9)) 212 | - The ESM version of the library is now `.mjs` (but we polyfill support for this and other `.mjs` files in webpack 4). 213 | - This library is now located within `node_modules` rather than being templated, with a limited template for the config. 214 | 215 | ### [0.22.4](https://github.com/nuxt-community/composition-api/compare/0.22.3...0.22.4) (2021-03-23) 216 | 217 | 218 | ### Bug Fixes 219 | 220 | * prevent resolution errors on old node versions ([df3e2fd](https://github.com/nuxt-community/composition-api/commit/df3e2fd78996fe5608668dacf30de5901ea59c72)) 221 | 222 | ### [0.22.3](https://github.com/nuxt-community/composition-api/compare/0.22.2...0.22.3) (2021-03-20) 223 | 224 | 225 | ### Bug Fixes 226 | 227 | * ensure that injected plugin is inserted first ([5cd13bc](https://github.com/nuxt-community/composition-api/commit/5cd13bc1813d7a8a67f41c7014a46cbbbbd2e3ff)), closes [#406](https://github.com/nuxt-community/composition-api/issues/406) 228 | 229 | ### [0.22.2](https://github.com/nuxt-community/composition-api/compare/0.22.1...0.22.2) (2021-03-20) 230 | 231 | ### [0.22.1](https://github.com/nuxt-community/composition-api/compare/0.22.0...0.22.1) (2021-03-11) 232 | 233 | ## [0.22.0](https://github.com/nuxt-community/composition-api/compare/0.21.0...0.22.0) (2021-03-05) 234 | 235 | 236 | ### Features 237 | 238 | * experimental vite support ([#387](https://github.com/nuxt-community/composition-api/issues/387)) ([e47805d](https://github.com/nuxt-community/composition-api/commit/e47805daab03543c81bf4f3efa72e6ef81dd799f)) 239 | 240 | 241 | ### Bug Fixes 242 | 243 | * transpile `@vue/composition-api` ([#391](https://github.com/nuxt-community/composition-api/issues/391)) ([7e6334f](https://github.com/nuxt-community/composition-api/commit/7e6334f2aad339fb74819681b72fa8783090fd22)) 244 | 245 | ## [0.21.0](https://github.com/nuxt-community/composition-api/compare/0.20.2...0.21.0) (2021-03-03) 246 | 247 | 248 | ### Features 249 | 250 | * upgrade @vue/composition-api to rc3 ([d4faf1d](https://github.com/nuxt-community/composition-api/commit/d4faf1defa5e9882676922dda4086f38a766b794)) 251 | 252 | 253 | ### Bug Fixes 254 | 255 | * build on Windows ([#377](https://github.com/nuxt-community/composition-api/issues/377)) ([f80ad03](https://github.com/nuxt-community/composition-api/commit/f80ad0308918662d52de559cdae660565c056683)) 256 | * use `upath` in more places for better windows support ([#388](https://github.com/nuxt-community/composition-api/issues/388)) ([fcd61db](https://github.com/nuxt-community/composition-api/commit/fcd61dbc00b658a7fd90a37be64fd155a9621941)) 257 | * use es module import as much as possible ([f7269b5](https://github.com/nuxt-community/composition-api/commit/f7269b55f47ccf34dcff83e1d57d99a6822ec9c3)), closes [#334](https://github.com/nuxt-community/composition-api/issues/334) 258 | 259 | ### [0.20.2](https://github.com/nuxt-community/composition-api/compare/0.20.1...0.20.2) (2021-02-18) 260 | 261 | 262 | ### Features 263 | 264 | * add new exports from composition-api ([#380](https://github.com/nuxt-community/composition-api/issues/380)) ([9cd5e93](https://github.com/nuxt-community/composition-api/commit/9cd5e93aa39015b3e95fc29d55ae6e452411e1bd)) 265 | 266 | 267 | ### Bug Fixes 268 | 269 | * allow `ssrRef` in globalSetup ([fcf91a7](https://github.com/nuxt-community/composition-api/commit/fcf91a73b9195dde075b72d816fc8f71f52b3465)), closes [#379](https://github.com/nuxt-community/composition-api/issues/379) 270 | 271 | ### [0.20.1](https://github.com/nuxt-community/composition-api/compare/0.20.0...0.20.1) (2021-02-15) 272 | 273 | 274 | ### Bug Fixes 275 | 276 | * always return $fetch and $fetchState ([e0a9cf4](https://github.com/nuxt-community/composition-api/commit/e0a9cf4842ae558396bda40f60bb0919224f958c)), closes [#372](https://github.com/nuxt-community/composition-api/issues/372) 277 | 278 | ## [0.20.0](https://github.com/nuxt-community/composition-api/compare/0.19.1...0.20.0) (2021-02-14) 279 | 280 | 281 | ### ⚠ BREAKING CHANGES 282 | 283 | * split out types and imports 284 | 285 | ### Features 286 | 287 | * add `defineNuxtConfig` helper ([6484e97](https://github.com/nuxt-community/composition-api/commit/6484e97aca55bdfa14dd2633c8e2146358b186e6)) 288 | * add `useRoute`, `useRouter` and `useStore` wrapper functions ([80e6c08](https://github.com/nuxt-community/composition-api/commit/80e6c084528f8b513e5711643b7703804534a6ed)) 289 | * add `wrapProperty` helper information ([bc0e091](https://github.com/nuxt-community/composition-api/commit/bc0e091554e2e5a9e9001c1f030e53483c641953)) 290 | * do some very dodgy things to improve dx ([0998d66](https://github.com/nuxt-community/composition-api/commit/0998d66e7dba9dd3251d779bbca4110243e3fb77)), closes [#334](https://github.com/nuxt-community/composition-api/issues/334) 291 | * fetch improvements ([#366](https://github.com/nuxt-community/composition-api/issues/366)) ([d4bdbfb](https://github.com/nuxt-community/composition-api/commit/d4bdbfbae5c9dfe7314542e10a1b956c0aff0089)), closes [#364](https://github.com/nuxt-community/composition-api/issues/364) [nuxt/nuxt.js#8781](https://github.com/nuxt/nuxt.js/issues/8781) [#292](https://github.com/nuxt-community/composition-api/issues/292) 292 | 293 | 294 | ### Bug Fixes 295 | 296 | * correct `static-json` path with custom router base or cdnURL ([fca527e](https://github.com/nuxt-community/composition-api/commit/fca527e539a28df89adfd0866b83849253cbf14e)), closes [#351](https://github.com/nuxt-community/composition-api/issues/351) 297 | * create `static-json` folder before dev/build ([27e2ffa](https://github.com/nuxt-community/composition-api/commit/27e2ffac50f9199ababec628b1cb50702838986c)), closes [#337](https://github.com/nuxt-community/composition-api/issues/337) 298 | * ensure `defineNuxtConfig` is accessible within config file ([06500ab](https://github.com/nuxt-community/composition-api/commit/06500ab07581b40f4f754b7ec486d3f283f643cf)) 299 | * split out types and imports ([cc81ad6](https://github.com/nuxt-community/composition-api/commit/cc81ad61c50bca4d966604e110ef77331bc83f3a)), closes [#334](https://github.com/nuxt-community/composition-api/issues/334) 300 | * type issues with new wrapper functions ([fb2aa09](https://github.com/nuxt-community/composition-api/commit/fb2aa0906991cb2d63cc1a292e02c5bc400b6a81)) 301 | * use es module version of entrypoint in build ([72e753a](https://github.com/nuxt-community/composition-api/commit/72e753a29edf35e86c0e26469b1a66a9f679a40c)) 302 | 303 | ### [0.19.1](https://github.com/nuxt-community/composition-api/compare/0.19.0...0.19.1) (2021-02-04) 304 | 305 | 306 | ### Bug Fixes 307 | 308 | * set `useFetch` delay to 0 by default ([#362](https://github.com/nuxt-community/composition-api/issues/362)) ([33b9790](https://github.com/nuxt-community/composition-api/commit/33b979043356f3406766389b61ee32002f68127d)) 309 | 310 | ## [0.19.0](https://github.com/nuxt-community/composition-api/compare/0.18.1...0.19.0) (2021-01-25) 311 | 312 | 313 | ### Features 314 | 315 | * add compositionAPI flag in jsx babel configuration ([#304](https://github.com/nuxt-community/composition-api/issues/304)) ([fec4218](https://github.com/nuxt-community/composition-api/commit/fec4218aa623f31556dad776c7af936abbaf87b8)) 316 | 317 | 318 | ### Bug Fixes 319 | 320 | * don't import core-js polyfill in server build ([#353](https://github.com/nuxt-community/composition-api/issues/353)) ([2755d6b](https://github.com/nuxt-community/composition-api/commit/2755d6beeec2cfb4d28ba7e300362286e38876d9)) 321 | * revert `siroc` to fix windows error ([#356](https://github.com/nuxt-community/composition-api/issues/356)) ([acdf18d](https://github.com/nuxt-community/composition-api/commit/acdf18ddccc015d8ce531631ddaea62dfb12efd0)) 322 | 323 | ### [0.18.1](https://github.com/nuxt-community/composition-api/compare/0.18.0...0.18.1) (2021-01-14) 324 | 325 | ## [0.18.0](https://github.com/nuxt-community/composition-api/compare/0.17.0...0.18.0) (2021-01-08) 326 | 327 | 328 | ### ⚠ BREAKING CHANGES 329 | 330 | * upgrade `@vue/composition-api` 331 | 332 | ### Miscellaneous Chores 333 | 334 | * upgrade `@vue/composition-api` ([a8fa46d](https://github.com/nuxt-community/composition-api/commit/a8fa46d74d00d84ee0055dc444b07aa0ad2dbbdf)) 335 | 336 | ## [0.17.0](https://github.com/nuxt-community/composition-api/compare/0.16.4...0.17.0) (2020-12-12) 337 | 338 | 339 | ### Features 340 | 341 | * upgrade to vca beta 21 (adds `readonly`) ([9277489](https://github.com/nuxt-community/composition-api/commit/92774896b27548352de75344ce319d1d899718e2)) 342 | 343 | 344 | ### Bug Fixes 345 | 346 | * ensure `ssrRef` doesn't share state across requests when used in `setup()` ([#310](https://github.com/nuxt-community/composition-api/issues/310)) ([61a3b55](https://github.com/nuxt-community/composition-api/commit/61a3b550afa1ffbeb285aad69c0cba62234da9be)) 347 | 348 | ### [0.16.4](https://github.com/nuxt-community/composition-api/compare/0.16.3...0.16.4) (2020-11-25) 349 | 350 | 351 | ### Bug Fixes 352 | 353 | * completely disable support for global `useMeta` if `@nuxtjs/pwa` detected ([5dc5e34](https://github.com/nuxt-community/composition-api/commit/5dc5e345500f437bb4332cee541dfe4af59e8254)) 354 | 355 | ### [0.16.3](https://github.com/nuxt-community/composition-api/compare/0.16.2...0.16.3) (2020-11-25) 356 | 357 | 358 | ### Features 359 | 360 | * respect explicitly set `build.corejs` version in `nuxt.config` ([b1603cb](https://github.com/nuxt-community/composition-api/commit/b1603cbbcca3aa99f7fffc2f408011c42861912e)) 361 | 362 | ### [0.16.2](https://github.com/nuxt-community/composition-api/compare/0.16.1...0.16.2) (2020-11-25) 363 | 364 | 365 | ### Bug Fixes 366 | 367 | * ensure meta plugin is loaded last ([c5c7299](https://github.com/nuxt-community/composition-api/commit/c5c72997f755b51b5d0d08e4807581df1f0c919c)) 368 | 369 | ### [0.16.1](https://github.com/nuxt-community/composition-api/compare/0.16.0...0.16.1) (2020-11-25) 370 | 371 | 372 | ### Bug Fixes 373 | 374 | * register meta plugin after other plugins ([87535eb](https://github.com/nuxt-community/composition-api/commit/87535eb5301d6640f91c8ea6fe621df096213418)), closes [#307](https://github.com/nuxt-community/composition-api/issues/307) 375 | 376 | ## [0.16.0](https://github.com/nuxt-community/composition-api/compare/0.15.1...0.16.0) (2020-11-24) 377 | 378 | 379 | ### Features 380 | 381 | * allow accessing meta properties in `onGlobalSetup` ([15cf20e](https://github.com/nuxt-community/composition-api/commit/15cf20ece6d49e4067f8d648b626c373e76129c0)), closes [#305](https://github.com/nuxt-community/composition-api/issues/305) 382 | 383 | 384 | ### Bug Fixes 385 | 386 | * **docs:** update lockfile ([952259b](https://github.com/nuxt-community/composition-api/commit/952259b04cb560e36731c68644f654ac83777e0d)) 387 | 388 | ### [0.15.1](https://github.com/nuxt-community/composition-api/compare/0.15.0...0.15.1) (2020-11-17) 389 | 390 | ## [0.15.0](https://github.com/nuxt-community/composition-api/compare/0.13.5...0.15.0) (2020-11-09) 391 | 392 | 393 | ### Features 394 | 395 | * add new export from upstream (`del`) ([4845254](https://github.com/nuxt-community/composition-api/commit/4845254b6fa25427ffcae459fec29d1596c78c97)) 396 | 397 | 398 | ### Bug Fixes 399 | 400 | * address TS bug ([e87d610](https://github.com/nuxt-community/composition-api/commit/e87d6108722df76446bc6eb041c00a0592ac4c18)) 401 | * use globalNuxt in full static mode ([1479665](https://github.com/nuxt-community/composition-api/commit/14796657f53d35134c6898a6e2aa9ab2640ee49c)) 402 | 403 | ## [0.14.0](https://github.com/nuxt-community/composition-api/compare/0.13.5...0.14.0) (2020-11-02) 404 | 405 | 406 | ### Features 407 | 408 | * add new export from upstream (`del`) ([4845254](https://github.com/nuxt-community/composition-api/commit/4845254b6fa25427ffcae459fec29d1596c78c97)) 409 | 410 | ### [0.13.5](https://github.com/nuxt-community/composition-api/compare/0.13.4...0.13.5) (2020-10-17) 411 | 412 | ### [0.13.4](https://github.com/nuxt-community/composition-api/compare/0.13.3...0.13.4) (2020-10-16) 413 | 414 | 415 | ### Features 416 | 417 | * add `reqRef` and `ssrReqRef` for refs to be reset per-request ([b6f327c](https://github.com/nuxt-community/composition-api/commit/b6f327ca55f1cd48f6b39c257b487a7d25a15f59)) 418 | 419 | ### [0.13.3](https://github.com/nuxt-community/composition-api/compare/0.13.2...0.13.3) (2020-10-15) 420 | 421 | 422 | ### Bug Fixes 423 | 424 | * fallback to instance for nuxt context ([d40c07b](https://github.com/nuxt-community/composition-api/commit/d40c07b7edf8190c8556866e418ee32100f5991a)), closes [#264](https://github.com/nuxt-community/composition-api/issues/264) 425 | 426 | ### [0.13.2](https://github.com/nuxt-community/composition-api/compare/0.13.1...0.13.2) (2020-10-15) 427 | 428 | 429 | ### Bug Fixes 430 | 431 | * reinitialise global setup callbacks on each request ([7816afe](https://github.com/nuxt-community/composition-api/commit/7816afe9ca46e3118ba18be34a88240d986ec75f)), closes [#270](https://github.com/nuxt-community/composition-api/issues/270) 432 | 433 | ### [0.13.1](https://github.com/nuxt-community/composition-api/compare/0.13.0...0.13.1) (2020-10-14) 434 | 435 | 436 | ### Bug Fixes 437 | 438 | * prevent `useMeta` from sharing state in ssr ([#268](https://github.com/nuxt-community/composition-api/issues/268)) ([e54cefe](https://github.com/nuxt-community/composition-api/commit/e54cefe61c28fbdb5956dc8ecf53e313567da1ee)) 439 | 440 | ## [0.13.0](https://github.com/nuxt-community/composition-api/compare/0.12.6...0.13.0) (2020-10-12) 441 | 442 | 443 | ### Features 444 | 445 | * allow computed properties within `useMeta` ([#255](https://github.com/nuxt-community/composition-api/issues/255)) ([7152ed4](https://github.com/nuxt-community/composition-api/commit/7152ed42785585507c26c7df6deced7c6e16f590)) 446 | 447 | 448 | ### Bug Fixes 449 | 450 | * use `siroc` to build ([79106c2](https://github.com/nuxt-community/composition-api/commit/79106c22a9da153c720251aef1dc3b7c66acb300)), closes [#259](https://github.com/nuxt-community/composition-api/issues/259) 451 | 452 | ### [0.12.6](https://github.com/nuxt-community/composition-api/compare/0.12.5...0.12.6) (2020-10-10) 453 | 454 | ### [0.12.5](https://github.com/nuxt-community/composition-api/compare/0.12.4...0.12.5) (2020-09-18) 455 | 456 | ### [0.12.4](https://github.com/nuxt-community/composition-api/compare/0.12.3...0.12.4) (2020-09-16) 457 | 458 | 459 | ### Bug Fixes 460 | 461 | * 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) 462 | 463 | ### [0.12.3](https://github.com/nuxt-community/composition-api/compare/0.12.2...0.12.3) (2020-08-26) 464 | 465 | 466 | ### Bug Fixes 467 | 468 | * 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)) 469 | * overwrite titleTemplate with undefined ([#220](https://github.com/nuxt-community/composition-api/issues/220)) ([dc6f253](https://github.com/nuxt-community/composition-api/commit/dc6f25360554aab4cb29ec8d0f021939c5660168)) 470 | 471 | ### [0.12.2](https://github.com/nuxt-community/composition-api/compare/0.12.1...0.12.2) (2020-08-18) 472 | 473 | 474 | ### Bug Fixes 475 | 476 | * 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) 477 | 478 | ### [0.12.1](https://github.com/nuxt-community/composition-api/compare/0.12.0...0.12.1) (2020-08-16) 479 | 480 | ## [0.12.0](https://github.com/nuxt-community/composition-api/compare/0.11.0...0.12.0) (2020-08-09) 481 | 482 | 483 | ### Features 484 | 485 | * add new exports from `@vue/composition-api` ([5bbcfd7](https://github.com/nuxt-community/composition-api/commit/5bbcfd75e241e3aa97d07ea87595b171019eec4c)) 486 | 487 | ## [0.11.0](https://github.com/nuxt-community/composition-api/compare/0.10.6...0.11.0) (2020-07-27) 488 | 489 | 490 | ### Features 491 | 492 | * add nuxt helpers ([#160](https://github.com/nuxt-community/composition-api/issues/160)) ([8a9885e](https://github.com/nuxt-community/composition-api/commit/8a9885e5a78dc3c07c38134e45887185be7d6139)) 493 | 494 | ### [0.10.6](https://github.com/nuxt-community/composition-api/compare/0.10.5...0.10.6) (2020-07-14) 495 | 496 | 497 | ### Bug Fixes 498 | 499 | * add support for windows ([#140](https://github.com/nuxt-community/composition-api/issues/140)) ([c39773f](https://github.com/nuxt-community/composition-api/commit/c39773fcff157169e2c7761ca7bbd229bf089331)) 500 | * correctly check for `isFullStatic` ([db87441](https://github.com/nuxt-community/composition-api/commit/db874411b9195d2d90fa1710fb9f4065a0fb153c)) 501 | 502 | ### [0.10.5](https://github.com/nuxt-community/composition-api/compare/0.10.4...0.10.5) (2020-07-09) 503 | 504 | 505 | ### Bug Fixes 506 | 507 | * create `static-json` dir at correct point in generate lifecycle ([2d4e10b](https://github.com/nuxt-community/composition-api/commit/2d4e10b645f982034a0e69a2d3ba338af9c0a255)) 508 | * 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) 509 | 510 | ### [0.10.4](https://github.com/nuxt-community/composition-api/compare/0.10.3...0.10.4) (2020-07-09) 511 | 512 | 513 | ### Bug Fixes 514 | 515 | * 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) 516 | 517 | ### [0.10.3](https://github.com/nuxt-community/composition-api/compare/0.10.2...0.10.3) (2020-07-01) 518 | 519 | 520 | ### Bug Fixes 521 | 522 | * check static path exists before copying files ([9f1bdc7](https://github.com/nuxt-community/composition-api/commit/9f1bdc7026336e018bec1f6b7d4b63713c451897)) 523 | 524 | ### [0.10.2](https://github.com/nuxt-community/composition-api/compare/0.10.1...0.10.2) (2020-06-25) 525 | 526 | 527 | ### Bug Fixes 528 | 529 | * remove `console.log` ([0b1d5a8](https://github.com/nuxt-community/composition-api/commit/0b1d5a8872760f28e4e7deb030cc6640b1b611cd)) 530 | 531 | ### [0.10.1](https://github.com/nuxt-community/composition-api/compare/0.10.0...0.10.1) (2020-06-25) 532 | 533 | 534 | ### Features 535 | 536 | * export `nextTick` from `@vue/composition-api` ([df68e0a](https://github.com/nuxt-community/composition-api/commit/df68e0a3788dbfca3d3f400465228daf3d202851)) 537 | 538 | ## [0.10.0](https://github.com/nuxt-community/composition-api/compare/0.9.3...0.10.0) (2020-06-25) 539 | 540 | 541 | ### ⚠ BREAKING CHANGES 542 | 543 | * requires nuxt 2.13+ for `useFetch` 544 | 545 | ### Features 546 | 547 | * 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) 548 | 549 | 550 | ### Bug Fixes 551 | 552 | * handle preexisting babel plugins function ([c5c338f](https://github.com/nuxt-community/composition-api/commit/c5c338f1675a05264ce3481e8469ad24d2fe44d8)) 553 | * specify type for inline function ([694f21b](https://github.com/nuxt-community/composition-api/commit/694f21bbfea097ea2409f70c8a9a8f700143b97f)) 554 | * write static files synchronously ([eb91a5f](https://github.com/nuxt-community/composition-api/commit/eb91a5f5d85f98b6bd5a425aab28c151183abe0b)) 555 | 556 | ### [0.9.3](https://github.com/nuxt-community/composition-api/compare/0.9.2...0.9.3) (2020-06-13) 557 | 558 | 559 | ### Features 560 | 561 | * add `ssrPromise` functionality ([461939d](https://github.com/nuxt-community/composition-api/commit/461939da38b0386c95b328ae49a10d2e57536aad)), closes [#115](https://github.com/nuxt-community/composition-api/issues/115) 562 | 563 | ### [0.9.2](https://github.com/nuxt-community/composition-api/compare/0.9.1...0.9.2) (2020-06-13) 564 | 565 | 566 | ### Features 567 | 568 | * 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) 569 | 570 | 571 | ### Bug Fixes 572 | 573 | * remove duplicate static path definition ([48b9afb](https://github.com/nuxt-community/composition-api/commit/48b9afbe0531ffa90090c67d69d8fc65f02fa868)) 574 | 575 | 576 | ### Performance Improvements 577 | 578 | * remove unnecessary spread ([2300a93](https://github.com/nuxt-community/composition-api/commit/2300a93d79e826526645d732f57bf9712f59e52e)) 579 | 580 | ### [0.9.1](https://github.com/nuxt-community/composition-api/compare/0.9.0...0.9.1) (2020-06-11) 581 | 582 | ## [0.9.0](https://github.com/nuxt-community/composition-api/compare/0.8.2...0.9.0) (2020-06-10) 583 | 584 | 585 | ### ⚠ BREAKING CHANGES 586 | 587 | * 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. 588 | 589 | ### Features 590 | 591 | * upgrade to `@vue/composition-api` v0.6.1 ([3d0bdc6](https://github.com/nuxt-community/composition-api/commit/3d0bdc62b509e0e95896ff8da79f12feaf0fb60b)) 592 | 593 | ### [0.8.2](https://github.com/nuxt-community/composition-api/compare/0.8.1...0.8.2) (2020-06-04) 594 | 595 | 596 | ### Bug Fixes 597 | 598 | * regression if `ssrRef` is used outside setup (in dev) ([f2beecd](https://github.com/nuxt-community/composition-api/commit/f2beecd38396a1942a52aceb55cc516eb9de37b7)) 599 | 600 | ### [0.8.1](https://github.com/nuxt-community/composition-api/compare/0.8.0...0.8.1) (2020-06-02) 601 | 602 | 603 | ### Bug Fixes 604 | 605 | * 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) 606 | 607 | ## [0.8.0](https://github.com/nuxt-community/composition-api/compare/0.7.5...0.8.0) (2020-05-30) 608 | 609 | 610 | ### Features 611 | 612 | * 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)) 613 | 614 | ### [0.7.5](https://github.com/nuxt-community/composition-api/compare/0.7.4...0.7.5) (2020-05-30) 615 | 616 | 617 | ### Bug Fixes 618 | 619 | * 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) 620 | 621 | ### [0.7.4](https://github.com/nuxt-community/composition-api/compare/0.7.3...0.7.4) (2020-05-28) 622 | 623 | 624 | ### Bug Fixes 625 | 626 | * transpile buildModule ([2d5388d](https://github.com/nuxt-community/composition-api/commit/2d5388de3433d157a71393cc121fd34ed1398dad)), closes [#80](https://github.com/nuxt-community/composition-api/issues/80) 627 | 628 | ### [0.7.3](https://github.com/nuxt-community/composition-api/compare/0.7.2...0.7.3) (2020-05-27) 629 | 630 | 631 | ### Bug Fixes 632 | 633 | * `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)) 634 | 635 | ### [0.7.2](https://github.com/nuxt-community/composition-api/compare/0.7.1...0.7.2) (2020-05-27) 636 | 637 | 638 | ### Bug Fixes 639 | 640 | * 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)) 641 | * 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) 642 | * sanitise ssrRefs ([fcb4a9d](https://github.com/nuxt-community/composition-api/commit/fcb4a9daca955b9537c6996f1de798b7eae6f94a)) 643 | 644 | ### [0.7.1](https://github.com/nuxt-community/composition-api/compare/0.7.0...0.7.1) (2020-05-19) 645 | 646 | 647 | ### Bug Fixes 648 | 649 | * 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) 650 | 651 | ## [0.7.0](https://github.com/nuxt-community/composition-api/compare/0.6.3...0.7.0) (2020-05-14) 652 | 653 | 654 | ### ⚠ BREAKING CHANGES 655 | 656 | * `route`, `query`, `from` and `params` are now returned as refs from `useContext`, which was probably what you wanted anyway. 657 | 658 | ### Bug Fixes 659 | 660 | * make route-related context reactive ([91292c8](https://github.com/nuxt-community/composition-api/commit/91292c8cb2d3cc2954ca67514ac59ab006f3ae73)) 661 | 662 | ### [0.6.3](https://github.com/nuxt-community/composition-api/compare/0.6.2...0.6.3) (2020-05-11) 663 | 664 | 665 | ### Features 666 | 667 | * create shallowSsrRef ([#49](https://github.com/nuxt-community/composition-api/issues/49)) ([5ef0f6c](https://github.com/nuxt-community/composition-api/commit/5ef0f6c8d6597e38b7396fdad9ef087eb4194eb6)) 668 | 669 | ### [0.6.2](https://github.com/nuxt-community/composition-api/compare/0.6.1...0.6.2) (2020-05-11) 670 | 671 | 672 | ### Features 673 | 674 | * use appropriate core-js polyfill for ie11 ([5281e66](https://github.com/nuxt-community/composition-api/commit/5281e66b1e77d762056909a2157ddbc72253fc71)) 675 | 676 | 677 | ### Bug Fixes 678 | 679 | * insert composition api plugin before others ([2ef608b](https://github.com/nuxt-community/composition-api/commit/2ef608b456fe945b47661d82af337dcd20c390c5)) 680 | * reduce meta type definitions ([fa9efa3](https://github.com/nuxt-community/composition-api/commit/fa9efa37c8eadd71c523a7cdbe0e574e210f4f8b)) 681 | * revert data sanitisation ([1a4bbed](https://github.com/nuxt-community/composition-api/commit/1a4bbed5025b9f1deb8294d568cf419d27b99a5e)) 682 | * sanitise `ssrRef` data ([d86fdb2](https://github.com/nuxt-community/composition-api/commit/d86fdb2ec6b00f437fe2c1786133b18fffe78a3b)) 683 | 684 | ### [0.6.1](https://github.com/nuxt-community/composition-api/compare/0.6.0...0.6.1) (2020-05-10) 685 | 686 | 687 | ### Features 688 | 689 | * 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)) 690 | 691 | 692 | ### Bug Fixes 693 | 694 | * improve default values for `useMeta` ([f3024e1](https://github.com/nuxt-community/composition-api/commit/f3024e1e79a01a2d9714b4549bd6d6966b2ef260)) 695 | 696 | ## [0.6.0](https://github.com/nuxt-community/composition-api/compare/0.5.0...0.6.0) (2020-05-09) 697 | 698 | 699 | ### ⚠ BREAKING CHANGES 700 | 701 | * `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. 702 | 703 | _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). 704 | 705 | ### Features 706 | 707 | * 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) 708 | * return `$fetch` and `$fetchState` from `useFetch` ([c45177f](https://github.com/nuxt-community/composition-api/commit/c45177f341c57fd40136aaac8e0c55e97c4edd4a)) 709 | 710 | ## [0.5.0](https://github.com/nuxt-community/composition-api/compare/0.4.1...0.5.0) (2020-05-08) 711 | 712 | 713 | ### Features 714 | 715 | * 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)) 716 | 717 | 718 | ### Bug Fixes 719 | 720 | * address cross-request state pollution ([b7248c7](https://github.com/nuxt-community/composition-api/commit/b7248c7bf87a7815180629247ccc65f235746565)) 721 | * remove no-setup `ssrRefs` until solution ([707fb25](https://github.com/nuxt-community/composition-api/commit/707fb25ee4243d28b4a43eb8e41b4eac37134492)) 722 | 723 | 724 | ### Performance Improvements 725 | 726 | * improve memory usage ([e3f7221](https://github.com/nuxt-community/composition-api/commit/e3f722187793dfab1dcb0d99e70261b574ceb97c)) 727 | 728 | ### [0.4.1](https://github.com/nuxt-community/composition-api/compare/0.4.0...0.4.1) (2020-05-08) 729 | 730 | 731 | ### Bug Fixes 732 | 733 | * 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) 734 | 735 | ## [0.4.0](https://github.com/nuxt-community/composition-api/compare/0.3.2...0.4.0) (2020-05-08) 736 | 737 | 738 | ### ⚠ BREAKING CHANGES 739 | 740 | * `withContext` is now deprecated 741 | 742 | ### Features 743 | 744 | * 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) 745 | * 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)) 746 | 747 | 748 | ### Bug Fixes 749 | 750 | * correctly type `ssrRef` with factory function ([7b734ac](https://github.com/nuxt-community/composition-api/commit/7b734ac038cde3fbf967b89a39e2f57844f046e1)) 751 | * make `useContext` API available ([#39](https://github.com/nuxt-community/composition-api/issues/39)) ([dc4f028](https://github.com/nuxt-community/composition-api/commit/dc4f028a2adcc5ee9d663ee9fe2217dd891a7fdf)) 752 | 753 | ### [0.3.2](https://github.com/nuxt-community/composition-api/compare/0.3.1...0.3.2) (2020-05-04) 754 | 755 | 756 | ### Bug Fixes 757 | 758 | * purge non-stringifiable values from ssrRefs ([ac199b1](https://github.com/nuxt-community/composition-api/commit/ac199b18b722774ef1b50936565cafcbc8689e1a)) 759 | 760 | ### [0.3.1](https://github.com/nuxt-community/composition-api/compare/0.3.0...0.3.1) (2020-05-04) 761 | 762 | 763 | ### Bug Fixes 764 | 765 | * set `ssrContext` through plugin ([3ba85f5](https://github.com/nuxt-community/composition-api/commit/3ba85f5fc65dcc9e1e121db2a72fbd13e0cd6565)) 766 | * use own `onServerPrefetch` for `useFetch` ([60e23dd](https://github.com/nuxt-community/composition-api/commit/60e23dd4930156ff9b3c3025478aa53b02003a86)) 767 | 768 | ## [0.3.0](https://github.com/nuxt-community/composition-api/compare/0.2.3...0.3.0) (2020-05-04) 769 | 770 | 771 | ### Features 772 | 773 | * 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)) 774 | 775 | 776 | ### Reverts 777 | 778 | * Revert "docs: remove live demo link (Now is rate-limiting deploy)" ([bbd8661](https://github.com/nuxt-community/composition-api/commit/bbd86618310d897ca041015c0a39248a6ddaef69)) 779 | 780 | ### [0.2.3](https://github.com/danielroe/nuxt-composition-api/compare/0.2.2...0.2.3) (2020-05-01) 781 | 782 | 783 | ### Bug Fixes 784 | 785 | * respect `fetchOnServer` option ([368f33d](https://github.com/danielroe/nuxt-composition-api/commit/368f33d08a0391392bad50b075282902af0ee4cb)) 786 | 787 | ### [0.2.2](https://github.com/danielroe/nuxt-composition-api/compare/0.2.1...0.2.2) (2020-04-30) 788 | 789 | 790 | ### Bug Fixes 791 | 792 | * correctly match `$fetch` and `$fetchState` features ([e2d0442](https://github.com/danielroe/nuxt-composition-api/commit/e2d0442190055608e56eab83316acc08dfb17c4b)) 793 | 794 | ### [0.2.1](https://github.com/danielroe/nuxt-composition-api/compare/0.2.0...0.2.1) (2020-04-30) 795 | 796 | 797 | ### Bug Fixes 798 | 799 | * require `@vue/composition-api` ([f81182e](https://github.com/danielroe/nuxt-composition-api/commit/f81182e6cdc0b03b5f1f72885cc022aa20f01b36)) 800 | 801 | ## [0.2.0](https://github.com/danielroe/nuxt-composition-api/compare/0.1.5...0.2.0) (2020-04-30) 802 | 803 | 804 | ### Features 805 | 806 | * 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)) 807 | 808 | ### [0.1.5](https://github.com/danielroe/nuxt-composition-api/compare/0.1.4...0.1.5) (2020-04-29) 809 | 810 | 811 | ### Bug Fixes 812 | 813 | * disable browser build ([849edee](https://github.com/danielroe/nuxt-composition-api/commit/849edee610ee536e2755999f878e669019deb363)) 814 | 815 | ### [0.1.4](https://github.com/danielroe/nuxt-composition-api/compare/0.1.3...0.1.4) (2020-04-29) 816 | 817 | ### [0.1.3](https://github.com/danielroe/nuxt-composition-api/compare/0.1.2...0.1.3) (2020-04-28) 818 | 819 | 820 | ### Bug Fixes 821 | 822 | * set $fetchState if nonexistent ([d94c40e](https://github.com/danielroe/nuxt-composition-api/commit/d94c40eb8b581bd9f1ab888310985966c7126643)) 823 | 824 | ### [0.1.2](https://github.com/danielroe/nuxt-composition-api/compare/0.1.1...0.1.2) (2020-04-27) 825 | 826 | 827 | ### ⚠ BREAKING CHANGES 828 | 829 | * `onFetch` can now be accessed using `useFetch` 830 | 831 | ### Code Refactoring 832 | 833 | * rename onFetch to useFetch ([3647769](https://github.com/danielroe/nuxt-composition-api/commit/3647769b8db96f8dcc0463ea4a820eb712ef97ca)) 834 | 835 | ### 0.1.1 (2020-04-27) 836 | 837 | 838 | ### Features 839 | 840 | * add withContext hook ([179f0e1](https://github.com/danielroe/nuxt-composition-api/commit/179f0e1ab7b0d67499c1814c0101fd7037b66490)) 841 | --------------------------------------------------------------------------------