├── .github ├── FUNDING.yml └── workflows │ ├── release.yml │ └── ci.yml ├── .npmrc ├── nuxt.d.ts ├── CONTRIBUTING.md ├── pnpm-workspace.yaml ├── test ├── cases │ ├── basic │ │ ├── input.html │ │ ├── output.ssr.js │ │ └── output.csr.js │ ├── v-show-lazy │ │ ├── input.html │ │ ├── output.ssr.js │ │ └── output.csr.js │ ├── event-props │ │ ├── input.html │ │ ├── output.ssr.js │ │ └── output.csr.js │ ├── expressions │ │ ├── input.html │ │ ├── output.ssr.js │ │ └── output.csr.js │ └── dynamic-attrs │ │ ├── input.html │ │ ├── output.ssr.js │ │ └── output.csr.js └── index.test.ts ├── playgrounds ├── vite │ ├── src │ │ ├── main.ts │ │ ├── shims-vue.d.ts │ │ ├── HelloWorld.vue │ │ └── App.vue │ ├── index.html │ ├── tsconfig.json │ ├── package.json │ └── vite.config.ts └── nuxt │ ├── tsconfig.json │ ├── package.json │ ├── HelloWorld.vue │ ├── nuxt.config.ts │ └── app.vue ├── .gitignore ├── eslint.config.js ├── src ├── nuxt.ts └── index.ts ├── tsconfig.json ├── LICENSE ├── .vscode └── settings.json ├── package.json └── README.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [antfu] 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | ignore-workspace-root-check=true 2 | -------------------------------------------------------------------------------- /nuxt.d.ts: -------------------------------------------------------------------------------- 1 | export { default } from './dist/nuxt.d' 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Please refer to https://github.com/antfu/contribute 2 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - playgrounds/* 3 | - examples/* 4 | -------------------------------------------------------------------------------- /test/cases/basic/input.html: -------------------------------------------------------------------------------- 1 | 2 | Hello 3 | 4 | -------------------------------------------------------------------------------- /test/cases/v-show-lazy/input.html: -------------------------------------------------------------------------------- 1 | 2 | Hello 3 | 4 | -------------------------------------------------------------------------------- /test/cases/event-props/input.html: -------------------------------------------------------------------------------- 1 | 2 | Hello 3 | 4 | -------------------------------------------------------------------------------- /playgrounds/vite/src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './App.vue' 3 | 4 | createApp(App).mount('#app') 5 | -------------------------------------------------------------------------------- /test/cases/expressions/input.html: -------------------------------------------------------------------------------- 1 |
hello
2 | 3 |
world
4 | -------------------------------------------------------------------------------- /playgrounds/nuxt/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // https://nuxt.com/docs/guide/concepts/typescript 3 | "extends": "./.nuxt/tsconfig.json" 4 | } 5 | -------------------------------------------------------------------------------- /test/cases/dynamic-attrs/input.html: -------------------------------------------------------------------------------- 1 | 2 |
hello world
3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .cache 2 | .DS_Store 3 | .idea 4 | *.log 5 | *.tgz 6 | coverage 7 | dist 8 | lib-cov 9 | logs 10 | node_modules 11 | temp 12 | .nuxt 13 | .output 14 | -------------------------------------------------------------------------------- /playgrounds/vite/src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import type { DefineComponent } from 'vue' 3 | 4 | const component: DefineComponent 5 | export default component 6 | } 7 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import antfu from '@antfu/eslint-config' 3 | 4 | export default antfu( 5 | { 6 | ignores: [ 7 | 'test/**/output.*.*', 8 | ], 9 | }, 10 | { 11 | rules: { 12 | // overrides 13 | }, 14 | }, 15 | ) 16 | -------------------------------------------------------------------------------- /src/nuxt.ts: -------------------------------------------------------------------------------- 1 | import { transformLazyShow } from '.' 2 | 3 | export default function (_: any, nuxt: any) { 4 | nuxt.options.vue ||= {} 5 | nuxt.options.vue.compilerOptions ||= {} 6 | nuxt.options.vue.compilerOptions.nodeTransforms ||= [] 7 | nuxt.options.vue.compilerOptions.nodeTransforms.unshift(transformLazyShow) 8 | } 9 | -------------------------------------------------------------------------------- /playgrounds/vite/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Vite + Vue + TS 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /playgrounds/nuxt/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nuxt-app", 3 | "private": true, 4 | "scripts": { 5 | "build": "nuxt build", 6 | "dev": "nuxt dev", 7 | "generate": "nuxt generate", 8 | "preview": "nuxt preview", 9 | "postinstall": "nuxt prepare" 10 | }, 11 | "devDependencies": { 12 | "@nuxt/devtools": "^1.0.8", 13 | "nuxt": "^3.11.1" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test/cases/basic/output.ssr.js: -------------------------------------------------------------------------------- 1 | import { mergeProps as _mergeProps, createVNode as _createVNode } from "vue" 2 | import { ssrRenderAttrs as _ssrRenderAttrs } from "vue/server-renderer" 3 | 4 | export function ssrRender(_ctx, _push, _parent, _attrs) { 5 | if (_ctx.foo) { 6 | _push(` Hello `) 7 | } else { 8 | _push(``) 9 | } 10 | } -------------------------------------------------------------------------------- /test/cases/v-show-lazy/output.ssr.js: -------------------------------------------------------------------------------- 1 | import { mergeProps as _mergeProps, createVNode as _createVNode } from "vue" 2 | import { ssrRenderAttrs as _ssrRenderAttrs } from "vue/server-renderer" 3 | 4 | export function ssrRender(_ctx, _push, _parent, _attrs) { 5 | if (_ctx.foo) { 6 | _push(` Hello `) 7 | } else { 8 | _push(``) 9 | } 10 | } -------------------------------------------------------------------------------- /playgrounds/nuxt/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 22 | -------------------------------------------------------------------------------- /test/cases/expressions/output.ssr.js: -------------------------------------------------------------------------------- 1 | import { createVNode as _createVNode } from "vue" 2 | 3 | export function ssrRender(_ctx, _push, _parent, _attrs) { 4 | _push(``) 5 | if (_ctx.foo === 1 || _ctx.bar === 2) { 6 | _push(`
hello
`) 7 | } else { 8 | _push(``) 9 | } 10 | if (!_ctx.a) { 11 | _push(`
world
`) 12 | } else { 13 | _push(``) 14 | } 15 | _push(``) 16 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2018", 4 | "lib": ["esnext", "dom"], 5 | "module": "esnext", 6 | "moduleResolution": "node", 7 | "resolveJsonModule": true, 8 | "types": [ 9 | "vite/client" 10 | ], 11 | "strict": true, 12 | "strictNullChecks": true, 13 | "esModuleInterop": true, 14 | "skipDefaultLibCheck": true, 15 | "skipLibCheck": true 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /playgrounds/vite/src/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 22 | -------------------------------------------------------------------------------- /playgrounds/vite/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "jsx": "preserve", 5 | "lib": ["ESNext", "DOM"], 6 | "useDefineForClassFields": true, 7 | "module": "ESNext", 8 | "moduleResolution": "Node", 9 | "resolveJsonModule": true, 10 | "strict": true, 11 | "noEmit": true, 12 | "esModuleInterop": true, 13 | "isolatedModules": true, 14 | "skipLibCheck": true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/cases/event-props/output.ssr.js: -------------------------------------------------------------------------------- 1 | import { mergeProps as _mergeProps, createVNode as _createVNode } from "vue" 2 | import { ssrRenderAttrs as _ssrRenderAttrs } from "vue/server-renderer" 3 | 4 | export function ssrRender(_ctx, _push, _parent, _attrs) { 5 | if (_ctx.foo) { 6 | _push(` Hello `) 10 | } else { 11 | _push(``) 12 | } 13 | } -------------------------------------------------------------------------------- /playgrounds/vite/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "playground", 3 | "type": "module", 4 | "version": "0.0.0", 5 | "private": true, 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vue-tsc && vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "vue": "^3.4.21" 13 | }, 14 | "devDependencies": { 15 | "@vitejs/plugin-vue": "^5.0.4", 16 | "typescript": "^5.4.2", 17 | "vite": "^5.1.6", 18 | "vue-tsc": "^2.0.6" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /test/cases/dynamic-attrs/output.ssr.js: -------------------------------------------------------------------------------- 1 | import { mergeProps as _mergeProps, createVNode as _createVNode } from "vue" 2 | import { ssrRenderAttrs as _ssrRenderAttrs } from "vue/server-renderer" 3 | 4 | export function ssrRender(_ctx, _push, _parent, _attrs) { 5 | _push(``) 6 | if (_ctx.isOpen) { 7 | _push(`hello world`) 8 | } else { 9 | _push(``) 10 | } 11 | _push(``) 12 | } -------------------------------------------------------------------------------- /playgrounds/vite/vite.config.ts: -------------------------------------------------------------------------------- 1 | import Vue from '@vitejs/plugin-vue' 2 | import { defineConfig } from 'vite' 3 | import Inspect from 'vite-plugin-inspect' 4 | import { transformLazyShow } from '../../src' 5 | 6 | // https://vitejs.dev/config/ 7 | export default defineConfig({ 8 | plugins: [ 9 | Vue({ 10 | template: { 11 | compilerOptions: { 12 | hoistStatic: false, 13 | nodeTransforms: [ 14 | transformLazyShow, 15 | ], 16 | }, 17 | }, 18 | }), 19 | Inspect(), 20 | ], 21 | }) 22 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | permissions: 4 | contents: write 5 | 6 | on: 7 | push: 8 | tags: 9 | - 'v*' 10 | 11 | jobs: 12 | release: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | with: 17 | fetch-depth: 0 18 | 19 | - name: Install pnpm 20 | uses: pnpm/action-setup@v4 21 | 22 | - name: Set node 23 | uses: actions/setup-node@v4 24 | with: 25 | node-version: lts/* 26 | 27 | - run: npx changelogithub 28 | env: 29 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 30 | -------------------------------------------------------------------------------- /test/cases/basic/output.csr.js: -------------------------------------------------------------------------------- 1 | import { Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, createCommentVNode as _createCommentVNode, createElementVNode as _createElementVNode, vShow as _vShow, withDirectives as _withDirectives } from "vue" 2 | 3 | export function render(_ctx, _cache) { 4 | return (_cache._lazyshow1 || _ctx.foo) 5 | ? (_cache._lazyshow1 = true, (_openBlock(), _createElementBlock(_Fragment, null, [ 6 | _withDirectives(_createElementVNode("span", null, " Hello ", 512 /* NEED_PATCH */), [ 7 | [_vShow, _ctx.foo] 8 | ]) 9 | ], 64 /* STABLE_FRAGMENT */))) 10 | : _createCommentVNode("v-show-if", true) 11 | } -------------------------------------------------------------------------------- /test/cases/v-show-lazy/output.csr.js: -------------------------------------------------------------------------------- 1 | import { Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, createCommentVNode as _createCommentVNode, createElementVNode as _createElementVNode, vShow as _vShow, withDirectives as _withDirectives } from "vue" 2 | 3 | export function render(_ctx, _cache) { 4 | return (_cache._lazyshow1 || _ctx.foo) 5 | ? (_cache._lazyshow1 = true, (_openBlock(), _createElementBlock(_Fragment, null, [ 6 | _withDirectives(_createElementVNode("span", null, " Hello ", 512 /* NEED_PATCH */), [ 7 | [_vShow, _ctx.foo] 8 | ]) 9 | ], 64 /* STABLE_FRAGMENT */))) 10 | : _createCommentVNode("v-show-if", true) 11 | } -------------------------------------------------------------------------------- /test/cases/event-props/output.csr.js: -------------------------------------------------------------------------------- 1 | import { Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, createCommentVNode as _createCommentVNode, createElementVNode as _createElementVNode, vShow as _vShow, withDirectives as _withDirectives } from "vue" 2 | 3 | export function render(_ctx, _cache) { 4 | return (_cache._lazyshow1 || _ctx.foo) 5 | ? (_cache._lazyshow1 = true, (_openBlock(), _createElementBlock(_Fragment, null, [ 6 | _withDirectives(_createElementVNode("span", { 7 | onClick: _cache[1] || (_cache[1] = $event => (_ctx._ctx.handle && _ctx.handle(...args))), 8 | id: _ctx.id, 9 | class: "111" 10 | }, " Hello ", 8 /* PROPS */, ["id"]), [ 11 | [_vShow, _ctx.foo] 12 | ]) 13 | ], 64 /* STABLE_FRAGMENT */))) 14 | : _createCommentVNode("v-show-if", true) 15 | } -------------------------------------------------------------------------------- /playgrounds/nuxt/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | import type { Plugin } from 'vite' 2 | import vLazyShow from '../../src/nuxt' 3 | 4 | // https://nuxt.com/docs/api/configuration/nuxt-config 5 | // eslint-disable-next-line ts/ban-ts-comment 6 | // @ts-ignore 7 | export default defineNuxtConfig({ 8 | modules: [ 9 | '@nuxt/devtools', 10 | vLazyShow, 11 | ], 12 | devtools: { 13 | componentInspector: false, 14 | }, 15 | vite: { 16 | plugins: [ 17 | { 18 | name: 'log', 19 | enforce: 'post', 20 | transform(code, id, ctx) { 21 | if (id.includes('app.vue') && ctx?.ssr) { 22 | const line = `${'-'.repeat(15)}` 23 | console.log(line) 24 | console.log(id) 25 | console.log(line) 26 | console.log(code) 27 | console.log(line) 28 | } 29 | }, 30 | }, 31 | ], 32 | }, 33 | }) 34 | -------------------------------------------------------------------------------- /playgrounds/vite/src/App.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 37 | -------------------------------------------------------------------------------- /test/cases/dynamic-attrs/output.csr.js: -------------------------------------------------------------------------------- 1 | import { createCommentVNode as _createCommentVNode, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, createElementVNode as _createElementVNode, vShow as _vShow, withDirectives as _withDirectives } from "vue" 2 | 3 | export function render(_ctx, _cache) { 4 | return (_openBlock(), _createElementBlock(_Fragment, null, [ 5 | _createCommentVNode(" https://github.com/antfu/v-lazy-show/issues/3 "), 6 | (_cache._lazyshow1 || _ctx.isOpen) 7 | ? (_cache._lazyshow1 = true, (_openBlock(), _createElementBlock(_Fragment, null, [ 8 | _withDirectives(_createElementVNode("div", { role: _ctx.role }, "hello world", 8 /* PROPS */, ["role"]), [ 9 | [_vShow, _ctx.isOpen] 10 | ]) 11 | ], 64 /* STABLE_FRAGMENT */))) 12 | : _createCommentVNode("v-show-if", true) 13 | ], 2112 /* STABLE_FRAGMENT, DEV_ROOT_FRAGMENT */)) 14 | } -------------------------------------------------------------------------------- /playgrounds/nuxt/app.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Anthony Fu 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 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // Disable the default formatter, use eslint instead 3 | "prettier.enable": false, 4 | "editor.formatOnSave": false, 5 | 6 | // Auto fix 7 | "editor.codeActionsOnSave": { 8 | "source.fixAll": "explicit", 9 | "source.organizeImports": "never" 10 | }, 11 | 12 | // Silent the stylistic rules in you IDE, but still auto fix them 13 | "eslint.rules.customizations": [ 14 | { "rule": "style/*", "severity": "off" }, 15 | { "rule": "*-indent", "severity": "off" }, 16 | { "rule": "*-spacing", "severity": "off" }, 17 | { "rule": "*-spaces", "severity": "off" }, 18 | { "rule": "*-order", "severity": "off" }, 19 | { "rule": "*-dangle", "severity": "off" }, 20 | { "rule": "*-newline", "severity": "off" }, 21 | { "rule": "*quotes", "severity": "off" }, 22 | { "rule": "*semi", "severity": "off" } 23 | ], 24 | 25 | // Enable eslint for all supported languages 26 | "eslint.validate": [ 27 | "javascript", 28 | "javascriptreact", 29 | "typescript", 30 | "typescriptreact", 31 | "vue", 32 | "html", 33 | "markdown", 34 | "json", 35 | "jsonc", 36 | "yaml" 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /test/cases/expressions/output.csr.js: -------------------------------------------------------------------------------- 1 | import { Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, createCommentVNode as _createCommentVNode, createElementVNode as _createElementVNode, vShow as _vShow, withDirectives as _withDirectives } from "vue" 2 | 3 | export function render(_ctx, _cache) { 4 | return (_openBlock(), _createElementBlock(_Fragment, null, [ 5 | (_cache._lazyshow1 || _ctx.foo === 1 || _ctx.bar === 2) 6 | ? (_cache._lazyshow1 = true, (_openBlock(), _createElementBlock(_Fragment, null, [ 7 | _withDirectives(_createElementVNode("div", null, "hello", 512 /* NEED_PATCH */), [ 8 | [_vShow, _ctx.foo === 1 || _ctx.bar === 2] 9 | ]) 10 | ], 64 /* STABLE_FRAGMENT */))) 11 | : _createCommentVNode("v-show-if", true), 12 | (_cache._lazyshow2 || !_ctx.a) 13 | ? (_cache._lazyshow2 = true, (_openBlock(), _createElementBlock(_Fragment, null, [ 14 | _withDirectives(_createElementVNode("div", null, "world", 512 /* NEED_PATCH */), [ 15 | [_vShow, !_ctx.a] 16 | ]) 17 | ], 64 /* STABLE_FRAGMENT */))) 18 | : _createCommentVNode("v-show-if", true) 19 | ], 64 /* STABLE_FRAGMENT */)) 20 | } -------------------------------------------------------------------------------- /test/index.test.ts: -------------------------------------------------------------------------------- 1 | import type { CompilerOptions } from '@vue/compiler-core' 2 | import { getBaseTransformPreset } from '@vue/compiler-core' 3 | import { describe, expect, it } from 'vitest' 4 | import { compileTemplate } from 'vue/compiler-sfc' 5 | import { transformLazyShow } from '../src' 6 | 7 | function parseWithForTransform( 8 | template: string, 9 | options: CompilerOptions = {}, 10 | ssr = false, 11 | ) { 12 | const [nodeTransforms, directiveTransforms] = getBaseTransformPreset() 13 | 14 | const result = compileTemplate({ 15 | id: 'foo.vue', 16 | filename: 'foo.vue', 17 | source: template, 18 | ssr, 19 | ssrCssVars: [], 20 | compilerOptions: { 21 | nodeTransforms: [ 22 | transformLazyShow, 23 | ...nodeTransforms, 24 | ], 25 | directiveTransforms, 26 | ...options, 27 | }, 28 | }) 29 | 30 | return result 31 | } 32 | 33 | describe('cases', () => { 34 | const cases = import.meta.glob('./cases/**/input.html', { as: 'raw' }) 35 | for (const [path, fn] of Object.entries(cases)) { 36 | it(path, async () => { 37 | const csr = parseWithForTransform(await fn()).code 38 | await expect(csr).toMatchFileSnapshot(path.replace('input.html', 'output.csr.js')) 39 | const ssr = parseWithForTransform(await fn(), {}, true).code 40 | await expect(ssr).toMatchFileSnapshot(path.replace('input.html', 'output.ssr.js')) 41 | }) 42 | } 43 | }) 44 | 45 | describe('errors', () => { 46 | it('error on template', () => { 47 | expect(() => { 48 | parseWithForTransform( 49 | ` 50 | 53 | `, 54 | ) 55 | }).toThrowErrorMatchingInlineSnapshot(`[Error: v-lazy-show can not be used on