This repository was archived by the owner on Dec 5, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 98
add useStatic helper to inline results of API calls
#79
Merged
Merged
Changes from all commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
920193e
refactor: install package within `.nuxt`
danielroe 1b6236d
feat: add `useStatic`helper to save expensive calls when generating
danielroe 0b4280c
test: don't use jsonplaceholder in e2e tests
danielroe 47df071
fix: respect `router.base` for generated .json files
danielroe 6784a53
Merge branch 'master' into use-static
danielroe 759521e
Merge branch 'master' into use-static
danielroe 523bd2e
Merge branch 'master' into use-static
danielroe 0c44bc5
docs: update docs given workaround
danielroe de536fb
test: remove exception for SSG
danielroe 596ef90
refactor: improve babel plugin readability and use hex hash
danielroe 20cbada
test: use now function to improve fixture dx
danielroe 392eaa0
test: add test for 404 handling
danielroe 6914b0b
ci: handle fallback on now
danielroe 0be4a86
test: wait for network requests
danielroe 5a74d39
test: correct difference between ssg/ssr
danielroe 3caa251
test: use relative url with fetcher
danielroe 3ef4b71
docs: update inline documentation
danielroe 4cbecdf
docs: explain that server will cache result
danielroe File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| --- | ||
| --- | ||
|
|
||
| # `useStatic` | ||
|
|
||
| You can pre-run expensive functions using `useStatic`. | ||
|
|
||
| ```ts | ||
| import { defineComponent, useContext, useStatic, computed } from 'nuxt-composition-api' | ||
| import axios from 'axios' | ||
|
|
||
| export default defineComponent({ | ||
| setup() { | ||
| const { params } = useContext() | ||
| const id = computed(() => params.value.id) | ||
| const post = useStatic( | ||
| id => axios.get(`https://jsonplaceholder.typicode.com/posts/${id}`), | ||
| id, | ||
| 'post' | ||
| ) | ||
|
|
||
| return { post } | ||
| }, | ||
| }) | ||
| ``` | ||
|
|
||
| ## SSG | ||
| 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: | ||
|
|
||
| * On generate, the result of a `useStatic` call will be saved to a JSON file and copied into the `/dist` directory. | ||
| * On hard-reload of a generated page, the JSON will be inlined into the page and cached. | ||
| * 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. | ||
|
|
||
| ::: warning | ||
| If you are pregenerating some pages in your app note that you may need to increase `generate.interval`. (See [setup instructions](/setup.html).) | ||
| ::: | ||
|
|
||
| ## SSR | ||
| If the route is not pre-generated (including in dev mode), then: | ||
|
|
||
| * 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. | ||
| * On client navigation, the client will run the factory function. | ||
|
|
||
| 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. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,118 @@ | ||
| import { ssrRef } from './ssr-ref' | ||
| import { Ref, onServerPrefetch, watch, computed } from '@vue/composition-api' | ||
|
|
||
| const staticPath = '<%= options.staticPath %>' | ||
| const staticCache: Record<string, any> = {} | ||
|
|
||
| async function writeFile(key: string) { | ||
| if (process.client || !process.static) return | ||
|
|
||
| const { writeFileSync }: typeof import('fs') = process.client | ||
| ? '' | ||
| : require('fs') | ||
| const { join }: typeof import('path') = process.client ? '' : require('path') | ||
|
|
||
| try { | ||
| writeFileSync( | ||
| join(staticPath, `${key}.json`), | ||
| JSON.stringify(staticCache[key]) | ||
| ) | ||
| } catch (e) { | ||
| console.log(e) | ||
| } | ||
| } | ||
| /** | ||
| * You can pre-run expensive functions using `useStatic`. | ||
| * | ||
| * __SSG__ | ||
| * 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: | ||
|
|
||
| 1. On generate, the result of a `useStatic` call will be saved to a JSON file and copied into the `/dist` directory. | ||
| 2. On hard-reload of a generated page, the JSON will be inlined into the page and cached. | ||
| 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. | ||
|
|
||
| If you are pregenerating some pages in your app note that you may need to increase `generate.interval`. (See [setup instructions](https://composition-api.now.sh/setup.html).) | ||
|
|
||
| * | ||
| * __SSR__ | ||
| * If the route is not pre-generated (including in dev mode), then: | ||
|
|
||
| 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. | ||
| 2. On client navigation, the client will run the factory function. | ||
|
|
||
| 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. | ||
|
|
||
| * @param factory The async function that will populate the ref this function returns. It receives the param and keyBase (see below) as parameters. | ||
| * @param param A an optional param (such as an ID) to distinguish multiple API fetches using the same factory function. | ||
| * @param keyBase A key that should be unique across your project. If not provided, this will be auto-generated by `nuxt-composition-api`. | ||
| * @example | ||
| ```ts | ||
| import { defineComponent, useContext, useStatic, computed } from 'nuxt-composition-api' | ||
| import axios from 'axios' | ||
|
|
||
| export default defineComponent({ | ||
| setup() { | ||
| const { params } = useContext() | ||
| const id = computed(() => params.value.id) | ||
| const post = useStatic( | ||
| id => axios.get(`https://jsonplaceholder.typicode.com/posts/${id}`), | ||
| id, | ||
| 'post' | ||
| ) | ||
|
|
||
| return { post } | ||
| }, | ||
| }) | ||
| ``` | ||
| */ | ||
| export const useStatic = <T>( | ||
| factory: (param: string, key: string) => Promise<T>, | ||
| param: Ref<string> = { value: '' }, | ||
| keyBase: string | ||
| ): Ref<T | null> => { | ||
| const key = computed(() => `${keyBase}-${param.value}`) | ||
| const result = ssrRef<T | null>(null, key.value) | ||
|
|
||
| if (result.value) staticCache[key.value] = result.value | ||
|
|
||
| if (process.client) { | ||
| const onFailure = () => | ||
| factory(param.value, key.value).then(r => { | ||
| staticCache[key.value] = r | ||
| result.value = r | ||
| return | ||
| }) | ||
| watch(key, key => { | ||
| if (key in staticCache) { | ||
| result.value = staticCache[key] | ||
| return | ||
| } | ||
| /* eslint-disable promise/always-return */ | ||
| if (!process.static) onFailure() | ||
| else | ||
| fetch(`<%= options.publicPath %>${key}.json`) | ||
| .then(response => { | ||
| if (!response.ok) throw new Error('Response invalid.') | ||
| return response.json() | ||
| }) | ||
| .then(json => { | ||
| staticCache[key] = json | ||
| result.value = json | ||
| }) | ||
| .catch(onFailure) | ||
| /* eslint-enable */ | ||
| }) | ||
| } else { | ||
| if (key.value in staticCache) { | ||
| result.value = staticCache[key.value] | ||
| return result | ||
| } | ||
| onServerPrefetch(async () => { | ||
| result.value = await factory(param.value, key.value) | ||
| staticCache[key.value] = result.value | ||
| writeFile(key.value) | ||
| }) | ||
| } | ||
|
|
||
| return result | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| import { Selector } from 'testcafe' | ||
| import { | ||
| navigateTo, | ||
| expectOnPage, | ||
| getLogger, | ||
| } from './helpers' | ||
|
|
||
| const apiLogger = getLogger(/posts/) | ||
|
|
||
| const url = (id: string | number) => process.env.GENERATE ? `http://localhost:3000/posts-${id}.json` : `http://localhost:3000/api/posts/${id}` | ||
|
|
||
| // eslint-disable-next-line | ||
| fixture`useStatic`.beforeEach(async t => { | ||
| await t.addRequestHooks(apiLogger.logger) | ||
| apiLogger.logger.clear()} | ||
| ) | ||
|
|
||
| test('Shows data on ssr-loaded page', async t => { | ||
| await navigateTo('/static/1') | ||
| await expectOnPage('"id": "1"') | ||
| const count = await apiLogger.logger.count(Boolean) | ||
| await t.expect(count).eql(0) | ||
|
|
||
| await t.click(Selector('a').withText('home')) | ||
| await t.click(Selector('a').withText('static')) | ||
| await expectOnPage('"id": "1"') | ||
| const newCount = await apiLogger.logger.count(Boolean) | ||
| await t.expect(newCount).eql(count) | ||
| }) | ||
|
|
||
| test('Shows data on non-generated page', async t => { | ||
| await navigateTo('/static/3') | ||
| apiLogger.logger.clear() | ||
| await t.click(Selector('a').withText('Next')) | ||
| await t.expect(apiLogger.logger.count(Boolean)).eql(process.env.GENERATE ? 2 : 1) | ||
| }) | ||
|
|
||
| test('Shows appropriate data on client-loaded page', async t => { | ||
| await navigateTo('/') | ||
| await t.click(Selector('a').withText('static')) | ||
| await expectOnPage('"id": "1"') | ||
| await t.expect(apiLogger.logger.count(Boolean)).eql(1) | ||
| await apiLogger.expectToBeCalledWith(r => r.request.url === url(1)) | ||
|
|
||
| await t.click(Selector('a').withText('Next')) | ||
| await expectOnPage('"id": "2"') | ||
| await t.expect(apiLogger.logger.count(Boolean)).eql(2) | ||
| await apiLogger.expectToBeCalledWith(r => r.request.url === url(2)) | ||
| }) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| { | ||
| "name": "api" | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.