penambahan web socket
This commit is contained in:
No files matched your search
+21
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-present - Nuxt Team
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
[](https://nuxt.com)
|
||||
|
||||
# Nuxt
|
||||
|
||||
<p>
|
||||
<a href="https://www.npmjs.com/package/nuxt"><img src="https://img.shields.io/npm/v/nuxt.svg?style=flat&colorA=18181B&colorB=28CF8D" alt="Version"></a>
|
||||
<a href="https://www.npmjs.com/package/nuxt"><img src="https://img.shields.io/npm/dm/nuxt.svg?style=flat&colorA=18181B&colorB=28CF8D" alt="Downloads"></a>
|
||||
<a href="https://github.com/nuxt/nuxt/tree/main/LICENSE"><img src="https://img.shields.io/github/license/nuxt/nuxt.svg?style=flat&colorA=18181B&colorB=28CF8D" alt="License"></a>
|
||||
<a href="https://nuxt.com"><img src="https://img.shields.io/badge/Nuxt%20Docs-18181B?logo=nuxt" alt="Website"></a>
|
||||
<a href="https://chat.nuxt.dev"><img src="https://img.shields.io/badge/Nuxt%20Discord-18181B?logo=discord" alt="Discord"></a>
|
||||
<a href="https://securityscorecards.dev/"><img src="https://api.securityscorecards.dev/projects/github.com/nuxt/nuxt/badge" alt="Nuxt openssf scorecard score"></a>
|
||||
</p>
|
||||
|
||||
Nuxt is a free and open-source framework with an intuitive and extendable way to create type-safe, performant and production-grade full-stack web applications and websites with Vue.js.
|
||||
|
||||
It provides a number of features that make it easy to build fast, SEO-friendly, and scalable web applications, including:
|
||||
- Server-side rendering, Static Site Generation, Hybrid Rendering and Edge-Side Rendering
|
||||
- Automatic routing with code-splitting and pre-fetching
|
||||
- Data fetching and state management
|
||||
- SEO Optimization and Meta tags definition
|
||||
- Auto imports of components, composables and utils
|
||||
- TypeScript with zero configuration
|
||||
- Go fullstack with our server/ directory
|
||||
- Extensible with [200+ modules](https://nuxt.com/modules)
|
||||
- Deployment to a variety of [hosting platforms](https://nuxt.com/deploy)
|
||||
- ...[and much more](https://nuxt.com) 🚀
|
||||
|
||||
### Table of Contents
|
||||
|
||||
- 🚀 [Getting Started](#getting-started)
|
||||
- 💻 [ Vue Development](#vue-development)
|
||||
- 📖 [Documentation](#documentation)
|
||||
- 🧩 [Modules](#modules)
|
||||
- ❤️ [Contribute](#contribute)
|
||||
- 🏠 [Local Development](#local-development)
|
||||
- 🛟 [Professional Support](#professional-support)
|
||||
- 🔗 [Follow Us](#follow-us)
|
||||
- ⚖️ [License](#license)
|
||||
|
||||
---
|
||||
|
||||
## <a name="getting-started">🚀 Getting Started</a>
|
||||
|
||||
Use the following command to create a new starter project. This will create a starter project with all the necessary files and dependencies:
|
||||
|
||||
```bash
|
||||
npm create nuxt@latest <my-project>
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
> Discover also [nuxt.new](https://nuxt.new): Open a Nuxt starter on CodeSandbox, StackBlitz or locally to get up and running in a few seconds.
|
||||
|
||||
## <a name="vue-development">💻 Vue Development</a>
|
||||
|
||||
Simple, intuitive and powerful, Nuxt lets you write Vue components in a way that makes sense. Every repetitive task is automated, so you can focus on writing your full-stack Vue application with confidence.
|
||||
|
||||
Example of an `app.vue`:
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
useSeoMeta({
|
||||
title: 'Meet Nuxt',
|
||||
description: 'The Intuitive Vue Framework.'
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="app">
|
||||
<AppHeader />
|
||||
<NuxtPage />
|
||||
<AppFooter />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
#app {
|
||||
background-color: #020420;
|
||||
color: #00DC82;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
## <a name="documentation">📖 Documentation</a>
|
||||
|
||||
We highly recommend you take a look at the [Nuxt documentation](https://nuxt.com/docs) to level up. It’s a great resource for learning more about the framework. It covers everything from getting started to advanced topics.
|
||||
|
||||
## <a name="modules">🧩 Modules</a>
|
||||
|
||||
Discover our [list of modules](https://nuxt.com/modules) to supercharge your Nuxt project, created by the Nuxt team and community.
|
||||
|
||||
## <a name="contribute">❤️ Contribute</a>
|
||||
|
||||
We invite you to contribute and help improve Nuxt 💚
|
||||
|
||||
Here are a few ways you can get involved:
|
||||
- **Reporting Bugs:** If you come across any bugs or issues, please check out the [reporting bugs guide](https://nuxt.com/docs/community/reporting-bugs) to learn how to submit a bug report.
|
||||
- **Suggestions:** Have ideas to enhance Nuxt? We'd love to hear them! Check out the [contribution guide](https://nuxt.com/docs/community/contribution) to share your suggestions.
|
||||
- **Questions:** If you have questions or need assistance, the [getting help guide](https://nuxt.com/docs/community/getting-help) provides resources to help you out.
|
||||
|
||||
## <a name="local-development">🏠 Local Development</a>
|
||||
|
||||
Follow the docs to [Set Up Your Local Development Environment](https://nuxt.com/docs/community/framework-contribution#setup) to contribute to the framework and documentation.
|
||||
|
||||
## <a name="professional-support">🛟 Professional Support</a>
|
||||
|
||||
- Technical audit & consulting: [Nuxt Experts](https://nuxt.com/enterprise/support)
|
||||
- Custom development & more: [Nuxt Agencies Partners](https://nuxt.com/enterprise/agencies)
|
||||
|
||||
## <a name="follow-us">🔗 Follow Us</a>
|
||||
|
||||
<p valign="center">
|
||||
<a href="https://go.nuxt.com/discord"><img width="20px" src="https://github.com/nuxt/nuxt/tree/main/.github/assets/discord.svg" alt="Discord"></a> <a href="https://go.nuxt.com/x"><img width="20px" src="https://github.com/nuxt/nuxt/tree/main/.github/assets/twitter.svg" alt="Twitter"></a> <a href="https://go.nuxt.com/github"><img width="20px" src="https://github.com/nuxt/nuxt/tree/main/.github/assets/github.svg" alt="GitHub"></a> <a href="https://go.nuxt.com/bluesky"><img width="20px" src="https://github.com/nuxt/nuxt/tree/main/.github/assets/bluesky.svg" alt="Bluesky"></a>
|
||||
</p>
|
||||
|
||||
## <a name="license">⚖️ License</a>
|
||||
|
||||
[MIT](https://github.com/nuxt/nuxt/tree/main/LICENSE)
|
||||
Generated
Vendored
+39
@@ -0,0 +1,39 @@
|
||||
import { transform } from 'esbuild';
|
||||
import { visualizer } from 'rollup-plugin-visualizer';
|
||||
import defu from 'defu';
|
||||
|
||||
function AnalyzePlugin(nuxt) {
|
||||
const analyzeOptions = defu({}, nuxt.options.build.analyze);
|
||||
if (!analyzeOptions.enabled) {
|
||||
return [];
|
||||
}
|
||||
return [
|
||||
{
|
||||
name: "nuxt:analyze-minify",
|
||||
async generateBundle(_opts, outputBundle) {
|
||||
for (const _bundleId in outputBundle) {
|
||||
const bundle = outputBundle[_bundleId];
|
||||
if (!bundle || bundle.type !== "chunk") {
|
||||
continue;
|
||||
}
|
||||
const minifiedModuleEntryPromises = [];
|
||||
for (const [moduleId, module] of Object.entries(bundle.modules)) {
|
||||
minifiedModuleEntryPromises.push(
|
||||
transform(module.code || "", { minify: true }).then((result) => [moduleId, { ...module, code: result.code }])
|
||||
);
|
||||
}
|
||||
bundle.modules = Object.fromEntries(await Promise.all(minifiedModuleEntryPromises));
|
||||
}
|
||||
}
|
||||
},
|
||||
visualizer({
|
||||
...analyzeOptions,
|
||||
filename: "filename" in analyzeOptions ? analyzeOptions.filename.replace("{name}", "client") : void 0,
|
||||
title: "Client bundle stats",
|
||||
gzipSize: true,
|
||||
brotliSize: true
|
||||
})
|
||||
];
|
||||
}
|
||||
|
||||
export { AnalyzePlugin };
|
||||
Generated
Vendored
+261
@@ -0,0 +1,261 @@
|
||||
import { pathToFileURL } from 'node:url';
|
||||
import { existsSync } from 'node:fs';
|
||||
import { writeFile } from 'node:fs/promises';
|
||||
import { builtinModules } from 'node:module';
|
||||
import { resolve, normalize, isAbsolute } from 'pathe';
|
||||
import { genObjectFromRawEntries, genDynamicImport } from 'knitwork';
|
||||
import { debounce } from 'perfect-debounce';
|
||||
import { isIgnored, logger } from '@nuxt/kit';
|
||||
import { t as toArray, i as isCSS, w as writeManifest, h as hashId, u as uniq } from '../shared/vite-builder.BLNMG9bN.mjs';
|
||||
import { ExternalsDefaults, isExternal } from 'externality';
|
||||
import escapeStringRegexp from 'escape-string-regexp';
|
||||
import { withTrailingSlash } from 'ufo';
|
||||
import 'vite';
|
||||
import '@rollup/plugin-replace';
|
||||
import 'mlly';
|
||||
import 'pathe/utils';
|
||||
import 'exsolve';
|
||||
import 'pkg-types';
|
||||
import '@vitejs/plugin-vue';
|
||||
import '@vitejs/plugin-vue-jsx';
|
||||
import 'get-port-please';
|
||||
import 'defu';
|
||||
import 'unenv';
|
||||
import 'h3';
|
||||
import 'ohash';
|
||||
import 'magic-string';
|
||||
import 'node:net';
|
||||
import 'node:os';
|
||||
import 'node:buffer';
|
||||
import 'vite-node/server';
|
||||
import 'vue-bundle-renderer';
|
||||
import 'std-env';
|
||||
import 'consola/utils';
|
||||
import 'jiti';
|
||||
|
||||
function createIsExternal(viteServer, nuxt) {
|
||||
const externalOpts = {
|
||||
inline: [
|
||||
/virtual:/,
|
||||
/\.ts$/,
|
||||
...ExternalsDefaults.inline || [],
|
||||
...viteServer.config.ssr.noExternal && viteServer.config.ssr.noExternal !== true ? toArray(viteServer.config.ssr.noExternal) : []
|
||||
],
|
||||
external: [
|
||||
"#shared",
|
||||
new RegExp("^" + escapeStringRegexp(withTrailingSlash(resolve(nuxt.options.rootDir, nuxt.options.dir.shared)))),
|
||||
...viteServer.config.ssr.external || [],
|
||||
/node_modules/
|
||||
],
|
||||
resolve: {
|
||||
modules: nuxt.options.modulesDir,
|
||||
type: "module",
|
||||
extensions: [".ts", ".js", ".json", ".vue", ".mjs", ".jsx", ".tsx", ".wasm"]
|
||||
}
|
||||
};
|
||||
return (id) => isExternal(id, nuxt.options.rootDir, externalOpts);
|
||||
}
|
||||
|
||||
async function transformRequest(opts, id) {
|
||||
if (id && id.startsWith("/@id/__x00__")) {
|
||||
id = "\0" + id.slice("/@id/__x00__".length);
|
||||
}
|
||||
if (id && id.startsWith("/@id/")) {
|
||||
id = id.slice("/@id/".length);
|
||||
}
|
||||
if (id && !id.startsWith("/@fs/") && id.startsWith("/")) {
|
||||
const resolvedPath = resolve(opts.viteServer.config.root, "." + id);
|
||||
if (existsSync(resolvedPath)) {
|
||||
id = resolvedPath;
|
||||
}
|
||||
}
|
||||
id = id.replace(/^\/?(?=\w:)/, "/@fs/");
|
||||
const externalId = id.replace(/\?v=\w+$|^\/@fs/, "");
|
||||
if (await opts.isExternal(externalId)) {
|
||||
const path = builtinModules.includes(externalId.split("node:").pop()) ? externalId : isAbsolute(externalId) ? pathToFileURL(externalId).href : externalId;
|
||||
return {
|
||||
code: `(global, module, _, exports, importMeta, ssrImport, ssrDynamicImport, ssrExportAll) =>
|
||||
${genDynamicImport(path, { wrapper: false })}
|
||||
.then(r => {
|
||||
if (r.default && r.default.__esModule)
|
||||
r = r.default
|
||||
exports.default = r.default
|
||||
ssrExportAll(r)
|
||||
})
|
||||
.catch(e => {
|
||||
console.error(e)
|
||||
throw new Error(${JSON.stringify(`[vite dev] Error loading external "${id}".`)})
|
||||
})`,
|
||||
deps: [],
|
||||
dynamicDeps: []
|
||||
};
|
||||
}
|
||||
const res = await opts.viteServer.transformRequest(id, { ssr: true }).catch((err) => {
|
||||
logger.warn(`[SSR] Error transforming ${id}:`, err);
|
||||
}) || { code: "", deps: [], dynamicDeps: [] };
|
||||
const code = `async function (global, module, exports, __vite_ssr_exports__, __vite_ssr_import_meta__, __vite_ssr_import__, __vite_ssr_dynamic_import__, __vite_ssr_exportAll__) {
|
||||
${res.code || "/* empty */"};
|
||||
}`;
|
||||
return { code, deps: res.deps || [], dynamicDeps: res.dynamicDeps || [] };
|
||||
}
|
||||
async function transformRequestRecursive(opts, id, parent = "<entry>", chunks = {}) {
|
||||
if (chunks[id]) {
|
||||
chunks[id].parents.push(parent);
|
||||
return;
|
||||
}
|
||||
const res = await transformRequest(opts, id);
|
||||
const deps = uniq([...res.deps, ...res.dynamicDeps]);
|
||||
chunks[id] = {
|
||||
id,
|
||||
code: res.code,
|
||||
deps,
|
||||
parents: [parent]
|
||||
};
|
||||
for (const dep of deps) {
|
||||
await transformRequestRecursive(opts, dep, id, chunks);
|
||||
}
|
||||
return Object.values(chunks);
|
||||
}
|
||||
async function bundleRequest(opts, entryURL) {
|
||||
const chunks = await transformRequestRecursive(opts, entryURL);
|
||||
const listIds = (ids) => ids.map((id) => `// - ${id} (${hashId(id)})`).join("\n");
|
||||
const chunksCode = chunks.map((chunk) => `
|
||||
// --------------------
|
||||
// Request: ${chunk.id}
|
||||
// Parents:
|
||||
${listIds(chunk.parents)}
|
||||
// Dependencies:
|
||||
${listIds(chunk.deps)}
|
||||
// --------------------
|
||||
const ${hashId(chunk.id + "-" + chunk.code)} = ${chunk.code}
|
||||
`).join("\n");
|
||||
const manifestCode = `const __modules__ = ${genObjectFromRawEntries(chunks.map((chunk) => [chunk.id, hashId(chunk.id + "-" + chunk.code)]))}`;
|
||||
const ssrModuleLoader = `
|
||||
const __pendingModules__ = new Map()
|
||||
const __pendingImports__ = new Map()
|
||||
const __ssrContext__ = { global: globalThis }
|
||||
|
||||
function __ssrLoadModule__(url, urlStack = []) {
|
||||
const pendingModule = __pendingModules__.get(url)
|
||||
if (pendingModule) { return pendingModule }
|
||||
const modulePromise = __instantiateModule__(url, urlStack)
|
||||
__pendingModules__.set(url, modulePromise)
|
||||
modulePromise.catch(() => { __pendingModules__.delete(url) })
|
||||
.finally(() => { __pendingModules__.delete(url) })
|
||||
return modulePromise
|
||||
}
|
||||
|
||||
async function __instantiateModule__(url, urlStack) {
|
||||
const mod = __modules__[url]
|
||||
if (mod.stubModule) { return mod.stubModule }
|
||||
const stubModule = { [Symbol.toStringTag]: 'Module' }
|
||||
Object.defineProperty(stubModule, '__esModule', { value: true })
|
||||
mod.stubModule = stubModule
|
||||
// https://vitejs.dev/guide/api-hmr.html
|
||||
const importMeta = { url, hot: { accept() {}, prune() {}, dispose() {}, invalidate() {}, decline() {}, on() {} } }
|
||||
urlStack = urlStack.concat(url)
|
||||
const isCircular = url => urlStack.includes(url)
|
||||
const pendingDeps = []
|
||||
const ssrImport = async (dep) => {
|
||||
// TODO: Handle externals if dep[0] !== '.' | '/'
|
||||
if (!isCircular(dep) && !__pendingImports__.get(dep)?.some(isCircular)) {
|
||||
pendingDeps.push(dep)
|
||||
if (pendingDeps.length === 1) {
|
||||
__pendingImports__.set(url, pendingDeps)
|
||||
}
|
||||
await __ssrLoadModule__(dep, urlStack)
|
||||
if (pendingDeps.length === 1) {
|
||||
__pendingImports__.delete(url)
|
||||
} else {
|
||||
pendingDeps.splice(pendingDeps.indexOf(dep), 1)
|
||||
}
|
||||
}
|
||||
return __modules__[dep].stubModule
|
||||
}
|
||||
function ssrDynamicImport (dep) {
|
||||
// TODO: Handle dynamic import starting with . relative to url
|
||||
return ssrImport(dep)
|
||||
}
|
||||
|
||||
function ssrExportAll(sourceModule) {
|
||||
for (const key in sourceModule) {
|
||||
if (key !== 'default') {
|
||||
try {
|
||||
Object.defineProperty(stubModule, key, {
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
get() { return sourceModule[key] }
|
||||
})
|
||||
} catch (_err) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const cjsModule = {
|
||||
get exports () {
|
||||
return stubModule.default
|
||||
},
|
||||
set exports (v) {
|
||||
stubModule.default = v
|
||||
},
|
||||
}
|
||||
|
||||
await mod(
|
||||
__ssrContext__.global,
|
||||
cjsModule,
|
||||
stubModule.default,
|
||||
stubModule,
|
||||
importMeta,
|
||||
ssrImport,
|
||||
ssrDynamicImport,
|
||||
ssrExportAll
|
||||
)
|
||||
|
||||
return stubModule
|
||||
}
|
||||
`;
|
||||
const code = [
|
||||
chunksCode,
|
||||
manifestCode,
|
||||
ssrModuleLoader,
|
||||
`export default await __ssrLoadModule__(${JSON.stringify(entryURL)})`
|
||||
].join("\n\n");
|
||||
return {
|
||||
code,
|
||||
ids: chunks.map((i) => i.id)
|
||||
};
|
||||
}
|
||||
async function initViteDevBundler(ctx, onBuild) {
|
||||
const viteServer = ctx.ssrServer;
|
||||
const options = {
|
||||
viteServer,
|
||||
isExternal: createIsExternal(viteServer, ctx.nuxt)
|
||||
};
|
||||
const _doBuild = async () => {
|
||||
const start = Date.now();
|
||||
const { code, ids } = await bundleRequest(options, ctx.entry);
|
||||
await writeFile(resolve(ctx.nuxt.options.buildDir, "dist/server/server.mjs"), code, "utf-8");
|
||||
const manifestIds = [];
|
||||
for (const i of ids) {
|
||||
if (isCSS(i)) {
|
||||
manifestIds.push(i.slice(1));
|
||||
}
|
||||
}
|
||||
await writeManifest(ctx, manifestIds);
|
||||
const time = Date.now() - start;
|
||||
logger.success(`Vite server built in ${time}ms`);
|
||||
await onBuild();
|
||||
};
|
||||
const doBuild = debounce(_doBuild);
|
||||
await _doBuild();
|
||||
viteServer.watcher.on("all", (_event, file) => {
|
||||
file = normalize(file);
|
||||
if (file.indexOf(ctx.nuxt.options.buildDir) === 0 || isIgnored(file)) {
|
||||
return;
|
||||
}
|
||||
doBuild();
|
||||
});
|
||||
ctx.nuxt.hook("app:templatesGenerated", () => doBuild());
|
||||
}
|
||||
|
||||
export { initViteDevBundler };
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
import * as vite from 'vite';
|
||||
import { NuxtBuilder, Nuxt, ViteConfig } from '@nuxt/schema';
|
||||
|
||||
interface ViteBuildContext {
|
||||
nuxt: Nuxt;
|
||||
config: ViteConfig;
|
||||
entry: string;
|
||||
clientServer?: vite.ViteDevServer;
|
||||
ssrServer?: vite.ViteDevServer;
|
||||
}
|
||||
declare const bundle: NuxtBuilder['bundle'];
|
||||
|
||||
export { bundle };
|
||||
export type { ViteBuildContext };
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
import * as vite from 'vite';
|
||||
import { NuxtBuilder, Nuxt, ViteConfig } from '@nuxt/schema';
|
||||
|
||||
interface ViteBuildContext {
|
||||
nuxt: Nuxt;
|
||||
config: ViteConfig;
|
||||
entry: string;
|
||||
clientServer?: vite.ViteDevServer;
|
||||
ssrServer?: vite.ViteDevServer;
|
||||
}
|
||||
declare const bundle: NuxtBuilder['bundle'];
|
||||
|
||||
export { bundle };
|
||||
export type { ViteBuildContext };
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
export { b as bundle } from './shared/vite-builder.BLNMG9bN.mjs';
|
||||
import 'node:fs';
|
||||
import 'vite';
|
||||
import 'pathe';
|
||||
import '@nuxt/kit';
|
||||
import '@rollup/plugin-replace';
|
||||
import 'mlly';
|
||||
import 'ufo';
|
||||
import 'pathe/utils';
|
||||
import 'exsolve';
|
||||
import 'pkg-types';
|
||||
import '@vitejs/plugin-vue';
|
||||
import '@vitejs/plugin-vue-jsx';
|
||||
import 'get-port-please';
|
||||
import 'defu';
|
||||
import 'unenv';
|
||||
import 'h3';
|
||||
import 'ohash';
|
||||
import 'node:url';
|
||||
import 'magic-string';
|
||||
import 'node:fs/promises';
|
||||
import 'node:net';
|
||||
import 'node:os';
|
||||
import 'node:buffer';
|
||||
import 'vite-node/server';
|
||||
import 'vue-bundle-renderer';
|
||||
import 'std-env';
|
||||
import 'consola/utils';
|
||||
import 'escape-string-regexp';
|
||||
import 'node:module';
|
||||
import 'jiti';
|
||||
import 'knitwork';
|
||||
Generated
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
declare function _default(): Promise<import("vue-bundle-renderer").Manifest>;
|
||||
export default _default;
|
||||
Generated
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
// @ts-check
|
||||
import { viteNodeFetch } from './vite-node-shared.mjs'
|
||||
|
||||
export default () => viteNodeFetch.getManifest()
|
||||
Generated
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
/** @typedef {import('node:net').Socket} Socket */
|
||||
/** @typedef {import('../vite-node').ViteNodeFetch} ViteNodeFetch */
|
||||
/** @type {import('../vite-node').ViteNodeServerOptions} */
|
||||
export const viteNodeOptions: import("../vite-node").ViteNodeServerOptions;
|
||||
/**
|
||||
* @type {ViteNodeFetch}
|
||||
*/
|
||||
export const viteNodeFetch: ViteNodeFetch;
|
||||
export type Socket = import("node:net").Socket;
|
||||
export type ViteNodeFetch = import("../vite-node").ViteNodeFetch;
|
||||
Generated
Vendored
+321
@@ -0,0 +1,321 @@
|
||||
// @ts-check
|
||||
import net from 'node:net'
|
||||
import { Buffer } from 'node:buffer'
|
||||
import { isTest } from 'std-env'
|
||||
|
||||
/** @typedef {import('node:net').Socket} Socket */
|
||||
/** @typedef {import('../vite-node').ViteNodeFetch} ViteNodeFetch */
|
||||
|
||||
/** @type {import('../vite-node').ViteNodeServerOptions} */
|
||||
export const viteNodeOptions = JSON.parse(process.env.NUXT_VITE_NODE_OPTIONS || '{}')
|
||||
|
||||
/** @type {Map<number, { resolve: (value: any) => void, reject: (reason?: any) => void }>} */
|
||||
const pendingRequests = new Map()
|
||||
let requestIdCounter = 0
|
||||
/** @type {Socket | undefined} */
|
||||
let clientSocket
|
||||
/** @type {Promise<Socket> | undefined} */
|
||||
let currentConnectPromise
|
||||
const MAX_RETRY_ATTEMPTS = viteNodeOptions.maxRetryAttempts ?? 5
|
||||
const BASE_RETRY_DELAY_MS = viteNodeOptions.baseRetryDelay ?? 100
|
||||
const MAX_RETRY_DELAY_MS = viteNodeOptions.maxRetryDelay ?? 2000
|
||||
const REQUEST_TIMEOUT_MS = viteNodeOptions.requestTimeout ?? 60000
|
||||
|
||||
/**
|
||||
* Calculates exponential backoff delay with jitter.
|
||||
* @param {number} attempt - The current attempt number (0-based).
|
||||
* @returns {number} Delay in milliseconds.
|
||||
*/
|
||||
function calculateRetryDelay (attempt) {
|
||||
const exponentialDelay = BASE_RETRY_DELAY_MS * Math.pow(2, attempt)
|
||||
const jitter = Math.random() * 0.1 * exponentialDelay // Add 10% jitter
|
||||
return Math.min(exponentialDelay + jitter, MAX_RETRY_DELAY_MS)
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes or returns an existing IPC socket connection with retry logic.
|
||||
* @returns {Promise<Socket>} A promise that resolves with the connected socket.
|
||||
*/
|
||||
function connectSocket () {
|
||||
if (clientSocket && !clientSocket.destroyed) {
|
||||
return Promise.resolve(clientSocket)
|
||||
}
|
||||
|
||||
if (currentConnectPromise) {
|
||||
return currentConnectPromise
|
||||
}
|
||||
|
||||
const thisPromise = new Promise((resolve, reject) => {
|
||||
if (!viteNodeOptions.socketPath) {
|
||||
console.error('vite-node-shared: NUXT_VITE_NODE_OPTIONS.socketPath is not defined.')
|
||||
return reject(new Error('Vite Node IPC socket path not configured.'))
|
||||
}
|
||||
|
||||
const attemptConnection = (attempt = 0) => {
|
||||
const socket = net.createConnection(viteNodeOptions.socketPath)
|
||||
|
||||
const INITIAL_BUFFER_SIZE = 64 * 1024 // 64KB
|
||||
const MAX_BUFFER_SIZE = 1024 * 1024 * 1024 // 1GB
|
||||
|
||||
let buffer = Buffer.alloc(INITIAL_BUFFER_SIZE)
|
||||
let writeOffset = 0
|
||||
let readOffset = 0
|
||||
|
||||
// optimize socket for high-frequency IPC
|
||||
socket.setNoDelay(true)
|
||||
socket.setKeepAlive(true, 30000) // 30s
|
||||
|
||||
const cleanup = () => {
|
||||
socket.off('connect', onConnect)
|
||||
socket.off('data', onData)
|
||||
socket.off('error', onError)
|
||||
socket.off('close', onClose)
|
||||
}
|
||||
|
||||
const resetBuffer = () => {
|
||||
writeOffset = 0
|
||||
readOffset = 0
|
||||
}
|
||||
|
||||
const compactBuffer = () => {
|
||||
if (readOffset > 0) {
|
||||
const remainingData = writeOffset - readOffset
|
||||
if (remainingData > 0) {
|
||||
buffer.copy(buffer, 0, readOffset, writeOffset)
|
||||
}
|
||||
writeOffset = remainingData
|
||||
readOffset = 0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} additionalBytes
|
||||
*/
|
||||
const ensureBufferCapacity = (additionalBytes) => {
|
||||
const requiredSize = writeOffset + additionalBytes
|
||||
|
||||
if (requiredSize > MAX_BUFFER_SIZE) {
|
||||
throw new Error(`Buffer size limit exceeded: ${requiredSize} > ${MAX_BUFFER_SIZE}`)
|
||||
}
|
||||
|
||||
if (requiredSize > buffer.length) {
|
||||
// Try compacting first
|
||||
compactBuffer()
|
||||
|
||||
// ... then if we still need more space, grow the buffer
|
||||
if (writeOffset + additionalBytes > buffer.length) {
|
||||
const newSize = Math.min(
|
||||
Math.max(buffer.length * 2, requiredSize),
|
||||
MAX_BUFFER_SIZE,
|
||||
)
|
||||
const newBuffer = Buffer.alloc(newSize)
|
||||
buffer.copy(newBuffer, 0, 0, writeOffset)
|
||||
buffer = newBuffer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const onConnect = () => {
|
||||
clientSocket = socket
|
||||
resolve(socket)
|
||||
}
|
||||
|
||||
/** @param {Buffer} data */
|
||||
const onData = (data) => {
|
||||
try {
|
||||
ensureBufferCapacity(data.length)
|
||||
data.copy(buffer, writeOffset)
|
||||
writeOffset += data.length
|
||||
|
||||
while (writeOffset - readOffset >= 4) {
|
||||
const messageLength = buffer.readUInt32BE(readOffset)
|
||||
|
||||
if (writeOffset - readOffset < 4 + messageLength) {
|
||||
return // Wait for more data
|
||||
}
|
||||
|
||||
const message = buffer.subarray(readOffset + 4, readOffset + 4 + messageLength).toString('utf-8')
|
||||
readOffset += 4 + messageLength
|
||||
|
||||
try {
|
||||
const response = JSON.parse(message)
|
||||
const requestHandlers = pendingRequests.get(response.id)
|
||||
if (requestHandlers) {
|
||||
const { resolve: resolveRequest, reject: rejectRequest } = requestHandlers
|
||||
if (response.type === 'error') {
|
||||
const err = new Error(response.error.message)
|
||||
// @ts-ignore We are augmenting the error object
|
||||
err.stack = response.error.stack
|
||||
// @ts-ignore
|
||||
err.data = response.error.data
|
||||
// @ts-ignore
|
||||
err.statusCode = response.error.statusCode
|
||||
rejectRequest(err)
|
||||
} else {
|
||||
resolveRequest(response.data)
|
||||
}
|
||||
pendingRequests.delete(response.id)
|
||||
}
|
||||
} catch (parseError) {
|
||||
console.warn('vite-node-shared: Failed to parse IPC response:', parseError)
|
||||
// ignore malformed messages
|
||||
}
|
||||
}
|
||||
|
||||
// compact buffer periodically to prevent memory waste
|
||||
if (readOffset > buffer.length / 2) {
|
||||
compactBuffer()
|
||||
}
|
||||
} catch (error) {
|
||||
socket.destroy(error instanceof Error ? error : new Error('Buffer management error'))
|
||||
}
|
||||
}
|
||||
|
||||
/** @param {Error} err */
|
||||
const onError = (err) => {
|
||||
cleanup()
|
||||
resetBuffer()
|
||||
|
||||
if (attempt < MAX_RETRY_ATTEMPTS) {
|
||||
const delay = calculateRetryDelay(attempt)
|
||||
setTimeout(() => attemptConnection(attempt + 1), delay)
|
||||
} else {
|
||||
if (currentConnectPromise === thisPromise) {
|
||||
reject(err)
|
||||
}
|
||||
for (const { reject: rejectRequest } of pendingRequests.values()) {
|
||||
rejectRequest(err)
|
||||
}
|
||||
pendingRequests.clear()
|
||||
if (clientSocket === socket) { clientSocket = undefined }
|
||||
if (currentConnectPromise === thisPromise) { currentConnectPromise = undefined }
|
||||
}
|
||||
}
|
||||
|
||||
const onClose = () => {
|
||||
cleanup()
|
||||
resetBuffer()
|
||||
for (const { reject: rejectRequest } of pendingRequests.values()) {
|
||||
rejectRequest(new Error('IPC connection closed'))
|
||||
}
|
||||
pendingRequests.clear()
|
||||
if (clientSocket === socket) { clientSocket = undefined }
|
||||
if (currentConnectPromise === thisPromise) { currentConnectPromise = undefined }
|
||||
}
|
||||
|
||||
socket.on('connect', onConnect)
|
||||
socket.on('data', onData)
|
||||
socket.on('error', onError)
|
||||
socket.on('close', onClose)
|
||||
}
|
||||
|
||||
attemptConnection()
|
||||
})
|
||||
|
||||
currentConnectPromise = thisPromise
|
||||
return currentConnectPromise
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a request over the IPC socket with automatic reconnection.
|
||||
* @template {keyof import('../vite-node').ViteNodeRequestMap} T
|
||||
* @param {T} type - The type of the request.
|
||||
* @param {import('../vite-node').ViteNodeRequestMap[T]['request']} [payload] - The payload for the request.
|
||||
* @returns {Promise<import('../vite-node').ViteNodeRequestMap[T]['response']>} A promise that resolves with the response data.
|
||||
*/
|
||||
async function sendRequest (type, payload) {
|
||||
const requestId = requestIdCounter++
|
||||
let lastError
|
||||
|
||||
// retry the entire request (including reconnection) up to MAX_RETRY_ATTEMPTS times
|
||||
for (let requestAttempt = 0; requestAttempt <= MAX_RETRY_ATTEMPTS; requestAttempt++) {
|
||||
try {
|
||||
const socket = await connectSocket()
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeoutId = setTimeout(() => {
|
||||
pendingRequests.delete(requestId)
|
||||
reject(new Error(`Request timeout after ${REQUEST_TIMEOUT_MS}ms for type: ${type}`))
|
||||
}, REQUEST_TIMEOUT_MS)
|
||||
|
||||
pendingRequests.set(requestId, {
|
||||
resolve: (value) => {
|
||||
clearTimeout(timeoutId)
|
||||
resolve(value)
|
||||
},
|
||||
reject: (reason) => {
|
||||
clearTimeout(timeoutId)
|
||||
reject(reason)
|
||||
},
|
||||
})
|
||||
|
||||
const message = JSON.stringify({ id: requestId, type, payload })
|
||||
const messageBuffer = Buffer.from(message, 'utf-8')
|
||||
const messageLength = messageBuffer.length
|
||||
|
||||
// pre-allocate single buffer for length + message to avoid Buffer.concat()
|
||||
const fullMessage = Buffer.alloc(4 + messageLength)
|
||||
fullMessage.writeUInt32BE(messageLength, 0)
|
||||
messageBuffer.copy(fullMessage, 4)
|
||||
|
||||
try {
|
||||
socket.write(fullMessage)
|
||||
} catch (error) {
|
||||
clearTimeout(timeoutId)
|
||||
pendingRequests.delete(requestId)
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
lastError = error
|
||||
if (requestAttempt < MAX_RETRY_ATTEMPTS) {
|
||||
const delay = calculateRetryDelay(requestAttempt)
|
||||
await new Promise(resolve => setTimeout(resolve, delay))
|
||||
// clear current connection state to force reconnection
|
||||
if (clientSocket) {
|
||||
clientSocket.destroy()
|
||||
clientSocket = undefined
|
||||
}
|
||||
currentConnectPromise = undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw lastError || new Error('Request failed after all retry attempts')
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {ViteNodeFetch}
|
||||
*/
|
||||
export const viteNodeFetch = {
|
||||
getManifest () {
|
||||
return sendRequest('manifest')
|
||||
},
|
||||
getInvalidates () {
|
||||
return sendRequest('invalidates')
|
||||
},
|
||||
resolveId (id, importer) {
|
||||
return sendRequest('resolve', { id, importer })
|
||||
},
|
||||
fetchModule (moduleId) {
|
||||
return sendRequest('module', { moduleId })
|
||||
},
|
||||
ensureConnected () {
|
||||
return connectSocket()
|
||||
},
|
||||
}
|
||||
|
||||
// attempt to pre-establish the IPC connection to reduce latency on first request
|
||||
let preConnectAttempted = false
|
||||
function preConnect () {
|
||||
if (preConnectAttempted || !viteNodeOptions.socketPath) {
|
||||
return
|
||||
}
|
||||
preConnectAttempted = true
|
||||
|
||||
return connectSocket().catch(() => {})
|
||||
}
|
||||
|
||||
if (typeof process !== 'undefined' && !isTest) {
|
||||
setTimeout(preConnect, 100)
|
||||
}
|
||||
Generated
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
declare function _default(ssrContext: any): Promise<any>;
|
||||
export default _default;
|
||||
Generated
Vendored
+111
@@ -0,0 +1,111 @@
|
||||
// @ts-check
|
||||
|
||||
import { performance } from 'node:perf_hooks'
|
||||
import { createError } from 'h3'
|
||||
import { ViteNodeRunner } from 'vite-node/client'
|
||||
import { consola } from 'consola'
|
||||
import { viteNodeFetch, viteNodeOptions } from './vite-node-shared.mjs'
|
||||
|
||||
const runner = createRunner()
|
||||
|
||||
/** @type {(ssrContext: import('#app').NuxtSSRContext) => Promise<any>} */
|
||||
let render
|
||||
|
||||
/** @param {import('#app').NuxtSSRContext} ssrContext */
|
||||
export default async (ssrContext) => {
|
||||
// Workaround for stub mode
|
||||
// https://github.com/nuxt/framework/pull/3983
|
||||
// eslint-disable-next-line nuxt/prefer-import-meta,@typescript-eslint/no-deprecated
|
||||
process.server = true
|
||||
import.meta.server = true
|
||||
|
||||
// Invalidate cache for files changed since last rendering
|
||||
const invalidates = await viteNodeFetch.getInvalidates()
|
||||
const updates = runner.moduleCache.invalidateDepTree(invalidates)
|
||||
|
||||
// Execute SSR bundle on demand
|
||||
const start = performance.now()
|
||||
render = (updates.has(viteNodeOptions.entryPath) || !render) ? (await runner.executeFile(viteNodeOptions.entryPath)).default : render
|
||||
if (updates.size) {
|
||||
const time = Math.round((performance.now() - start) * 1000) / 1000
|
||||
consola.success(`Vite server hmr ${updates.size} files`, time ? `in ${time}ms` : '')
|
||||
}
|
||||
|
||||
const result = await render(ssrContext)
|
||||
return result
|
||||
}
|
||||
|
||||
function createRunner () {
|
||||
return new ViteNodeRunner({
|
||||
root: viteNodeOptions.root, // Equals to Nuxt `srcDir`
|
||||
base: viteNodeOptions.base,
|
||||
async resolveId (id, importer) {
|
||||
return await viteNodeFetch.resolveId(id, importer)
|
||||
},
|
||||
async fetchModule (id) {
|
||||
id = id.replace(/\/\//g, '/') // TODO: fix in vite-node
|
||||
return await viteNodeFetch.fetchModule(id).catch((err) => {
|
||||
const errorData = err?.data?.data
|
||||
if (!errorData) {
|
||||
throw err
|
||||
}
|
||||
let _err
|
||||
try {
|
||||
const { message, stack } = formatViteError(errorData, id)
|
||||
_err = createError({
|
||||
statusMessage: 'Vite Error',
|
||||
message,
|
||||
stack,
|
||||
})
|
||||
} catch (formatError) {
|
||||
consola.warn('Internal nuxt error while formatting vite-node error. Please report this!', formatError)
|
||||
const message = `[vite-node] [TransformError] ${errorData?.message || '-'}`
|
||||
consola.error(message, errorData)
|
||||
throw createError({
|
||||
statusMessage: 'Vite Error',
|
||||
message,
|
||||
stack: `${message}\nat ${id}\n` + (errorData?.stack || ''),
|
||||
})
|
||||
}
|
||||
throw _err
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {any} errorData
|
||||
* @param {string} id
|
||||
*/
|
||||
function formatViteError (errorData, id) {
|
||||
const errorCode = errorData.name || errorData.reasonCode || errorData.code
|
||||
const frame = errorData.frame || errorData.source || errorData.pluginCode
|
||||
|
||||
/** @param {{ file?: string, id?: string, url?: string }} locObj */
|
||||
const getLocId = (locObj = {}) => locObj.file || locObj.id || locObj.url || id || ''
|
||||
/** @param {{ line?: string, column?: string }} locObj */
|
||||
const getLocPos = (locObj = {}) => locObj.line ? `${locObj.line}:${locObj.column || 0}` : ''
|
||||
const locId = getLocId(errorData.loc) || getLocId(errorData.location) || getLocId(errorData.input) || getLocId(errorData)
|
||||
const locPos = getLocPos(errorData.loc) || getLocPos(errorData.location) || getLocPos(errorData.input) || getLocPos(errorData)
|
||||
const loc = locId.replace(process.cwd(), '.') + (locPos ? `:${locPos}` : '')
|
||||
|
||||
const message = [
|
||||
'[vite-node]',
|
||||
errorData.plugin && `[plugin:${errorData.plugin}]`,
|
||||
errorCode && `[${errorCode}]`,
|
||||
loc,
|
||||
errorData.reason && `: ${errorData.reason}`,
|
||||
frame && `<br><pre>${frame.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')}</pre><br>`,
|
||||
].filter(Boolean).join(' ')
|
||||
|
||||
const stack = [
|
||||
message,
|
||||
`at ${loc}`,
|
||||
errorData.stack,
|
||||
].filter(Boolean).join('\n')
|
||||
|
||||
return {
|
||||
message,
|
||||
stack,
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+1896
File diff suppressed because it is too large
Load diff
Generated
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) Pooya Parsa <[email protected]>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
Generated
Vendored
+119
@@ -0,0 +1,119 @@
|
||||
# perfect-debounce
|
||||
|
||||
<!-- automd:badges color=yellow codecov bundlephobia packagephobia -->
|
||||
|
||||
[](https://npmjs.com/package/perfect-debounce)
|
||||
[](https://npm.chart.dev/perfect-debounce)
|
||||
[](https://bundlephobia.com/package/perfect-debounce)
|
||||
[](https://packagephobia.com/result?p=perfect-debounce)
|
||||
[](https://codecov.io/gh/unjs/perfect-debounce)
|
||||
|
||||
<!-- /automd -->
|
||||
|
||||
Improved debounce function with Promise support.
|
||||
|
||||
## Features
|
||||
|
||||
- Well tested debounce implementation
|
||||
- Native Promise support
|
||||
- Avoid duplicate calls while promise is being resolved
|
||||
- Configurable `trailing` and `leading` behavior
|
||||
- Control methods
|
||||
|
||||
## Usage
|
||||
|
||||
Install package:
|
||||
|
||||
```sh
|
||||
npx nypm i perfect-debounce
|
||||
```
|
||||
|
||||
Import:
|
||||
|
||||
```js
|
||||
import { debounce } from "perfect-debounce";
|
||||
```
|
||||
|
||||
Debounce function:
|
||||
|
||||
```js
|
||||
const debounced = debounce(async () => {
|
||||
// Some heavy stuff
|
||||
}, 25);
|
||||
```
|
||||
|
||||
When calling `debounced`, it will wait at least for `25ms` as configured before actually calling your function. This helps to avoid multiple calls.
|
||||
|
||||
### Control Methods
|
||||
|
||||
The returned debounced function provides additional control methods:
|
||||
|
||||
- `debounced.cancel()`: Cancel any pending invocation that has not yet occurred.
|
||||
- `await debounced.flush()`: Immediately invoke the pending function call (if any) and return its result.
|
||||
- `debounced.isPending()`: Returns `true` if there is a pending invocation waiting to be called, otherwise `false`.
|
||||
|
||||
```js
|
||||
debounced.cancel(); // Cancel any pending call
|
||||
await debounced.flush(); // Immediately invoke pending call (if any)
|
||||
debounced.isPending(); // Returns true if a call is pending
|
||||
```
|
||||
|
||||
### Example
|
||||
|
||||
```js
|
||||
const debounced = debounce(async (value) => {
|
||||
// Some async work
|
||||
return value * 2;
|
||||
}, 100);
|
||||
|
||||
debounced(1);
|
||||
debounced(2);
|
||||
debounced(3);
|
||||
|
||||
// Check if a call is pending
|
||||
console.log(debounced.isPending()); // true
|
||||
|
||||
// Immediately invoke the pending call
|
||||
const result = await debounced.flush();
|
||||
console.log(result); // 6
|
||||
|
||||
// Cancel any further pending calls
|
||||
debounced.cancel();
|
||||
```
|
||||
|
||||
To avoid initial wait, we can set `leading: true` option. It will cause function to be immediately called if there is no other call:
|
||||
|
||||
```js
|
||||
const debounced = debounce(
|
||||
async () => {
|
||||
// Some heavy stuff
|
||||
},
|
||||
25,
|
||||
{ leading: true },
|
||||
);
|
||||
```
|
||||
|
||||
If executing async function takes longer than debounce value, duplicate calls will be still prevented a last call will happen. To disable this behavior, we can set `trailing: false` option:
|
||||
|
||||
```js
|
||||
const debounced = debounce(
|
||||
async () => {
|
||||
// Some heavy stuff
|
||||
},
|
||||
25,
|
||||
{ trailing: false },
|
||||
);
|
||||
```
|
||||
|
||||
## 💻 Development
|
||||
|
||||
- Clone this repository
|
||||
- Enable [Corepack](https://github.com/nodejs/corepack) using `corepack enable` (use `npm i -g corepack` for Node.js < 16.10)
|
||||
- Install dependencies using `pnpm install`
|
||||
- Run interactive tests using `pnpm dev`
|
||||
|
||||
## License
|
||||
|
||||
Based on [sindresorhus/p-debounce](https://github.com/sindresorhus/p-debounce).
|
||||
|
||||
Made with 💛 Published under [MIT License](./LICENSE).
|
||||
Generated
Vendored
+49
@@ -0,0 +1,49 @@
|
||||
//#region src/index.d.ts
|
||||
interface DebounceOptions {
|
||||
/**
|
||||
Call the `fn` on the [leading edge of the timeout](https://css-tricks.com/debouncing-throttling-explained-examples/#article-header-id-1).
|
||||
Meaning immediately, instead of waiting for `wait` milliseconds.
|
||||
@default false
|
||||
*/
|
||||
readonly leading?: boolean;
|
||||
/**
|
||||
Call the `fn` on trailing edge with last used arguments. Result of call is from previous call.
|
||||
@default true
|
||||
*/
|
||||
readonly trailing?: boolean;
|
||||
}
|
||||
type DebouncedReturn<ArgumentsT extends unknown[], ReturnT> = ((...args: ArgumentsT) => Promise<ReturnT>) & {
|
||||
/**
|
||||
* Cancel pending function call
|
||||
*/
|
||||
cancel: () => void;
|
||||
/**
|
||||
* Immediately invoke pending function call
|
||||
*/
|
||||
flush: () => Promise<ReturnT> | undefined;
|
||||
/**
|
||||
* Get pending function call
|
||||
*/
|
||||
isPending: () => boolean;
|
||||
};
|
||||
/**
|
||||
Debounce functions
|
||||
@param fn - Promise-returning/async function to debounce.
|
||||
@param wait - Milliseconds to wait before calling `fn`. Default value is 25ms
|
||||
@returns A function that delays calling `fn` until after `wait` milliseconds have elapsed since the last time it was called.
|
||||
@example
|
||||
```
|
||||
import { debounce } from 'perfect-debounce';
|
||||
const expensiveCall = async input => input;
|
||||
const debouncedFn = debounce(expensiveCall, 200);
|
||||
for (const number of [1, 2, 3]) {
|
||||
console.log(await debouncedFn(number));
|
||||
}
|
||||
//=> 1
|
||||
//=> 2
|
||||
//=> 3
|
||||
```
|
||||
*/
|
||||
declare function debounce<ArgumentsT extends unknown[], ReturnT>(fn: (...args: ArgumentsT) => PromiseLike<ReturnT> | ReturnT, wait?: number, options?: DebounceOptions): DebouncedReturn<ArgumentsT, ReturnT>;
|
||||
//#endregion
|
||||
export { DebounceOptions, debounce };
|
||||
Generated
Vendored
+89
@@ -0,0 +1,89 @@
|
||||
//#region src/index.ts
|
||||
const DEBOUNCE_DEFAULTS = { trailing: true };
|
||||
/**
|
||||
Debounce functions
|
||||
@param fn - Promise-returning/async function to debounce.
|
||||
@param wait - Milliseconds to wait before calling `fn`. Default value is 25ms
|
||||
@returns A function that delays calling `fn` until after `wait` milliseconds have elapsed since the last time it was called.
|
||||
@example
|
||||
```
|
||||
import { debounce } from 'perfect-debounce';
|
||||
const expensiveCall = async input => input;
|
||||
const debouncedFn = debounce(expensiveCall, 200);
|
||||
for (const number of [1, 2, 3]) {
|
||||
console.log(await debouncedFn(number));
|
||||
}
|
||||
//=> 1
|
||||
//=> 2
|
||||
//=> 3
|
||||
```
|
||||
*/
|
||||
function debounce(fn, wait = 25, options = {}) {
|
||||
options = {
|
||||
...DEBOUNCE_DEFAULTS,
|
||||
...options
|
||||
};
|
||||
if (!Number.isFinite(wait)) throw new TypeError("Expected `wait` to be a finite number");
|
||||
let leadingValue;
|
||||
let timeout;
|
||||
let resolveList = [];
|
||||
let currentPromise;
|
||||
let trailingArgs;
|
||||
const applyFn = (_this, args) => {
|
||||
currentPromise = _applyPromised(fn, _this, args);
|
||||
currentPromise.finally(() => {
|
||||
currentPromise = null;
|
||||
if (options.trailing && trailingArgs && !timeout) {
|
||||
const promise = applyFn(_this, trailingArgs);
|
||||
trailingArgs = null;
|
||||
return promise;
|
||||
}
|
||||
});
|
||||
return currentPromise;
|
||||
};
|
||||
const debounced = function(...args) {
|
||||
if (options.trailing) trailingArgs = args;
|
||||
if (currentPromise) return currentPromise;
|
||||
return new Promise((resolve) => {
|
||||
const shouldCallNow = !timeout && options.leading;
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => {
|
||||
timeout = null;
|
||||
const promise = options.leading ? leadingValue : applyFn(this, args);
|
||||
trailingArgs = null;
|
||||
for (const _resolve of resolveList) _resolve(promise);
|
||||
resolveList = [];
|
||||
}, wait);
|
||||
if (shouldCallNow) {
|
||||
leadingValue = applyFn(this, args);
|
||||
resolve(leadingValue);
|
||||
} else resolveList.push(resolve);
|
||||
});
|
||||
};
|
||||
const _clearTimeout = (timer) => {
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
timeout = null;
|
||||
}
|
||||
};
|
||||
debounced.isPending = () => !!timeout;
|
||||
debounced.cancel = () => {
|
||||
_clearTimeout(timeout);
|
||||
resolveList = [];
|
||||
trailingArgs = null;
|
||||
};
|
||||
debounced.flush = () => {
|
||||
_clearTimeout(timeout);
|
||||
if (!trailingArgs || currentPromise) return;
|
||||
const args = trailingArgs;
|
||||
trailingArgs = null;
|
||||
return applyFn(this, args);
|
||||
};
|
||||
return debounced;
|
||||
}
|
||||
async function _applyPromised(fn, _this, args) {
|
||||
return await fn.apply(_this, args);
|
||||
}
|
||||
|
||||
//#endregion
|
||||
export { debounce };
|
||||
Generated
Vendored
+41
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"name": "perfect-debounce",
|
||||
"version": "2.0.0",
|
||||
"description": "",
|
||||
"repository": "unjs/perfect-debounce",
|
||||
"license": "MIT",
|
||||
"sideEffects": false,
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./dist/index.mjs"
|
||||
},
|
||||
"main": "./dist/index.mjs",
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.mts",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "obuild",
|
||||
"dev": "vitest dev",
|
||||
"lint": "eslint . && prettier --check src test",
|
||||
"lint:fix": "eslint . --fix && prettier -w src test",
|
||||
"release": "pnpm test && pnpm build && changelogen --release --push && npm publish",
|
||||
"test": "vitest run --coverage"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.3.0",
|
||||
"@vitest/coverage-v8": "^3.2.4",
|
||||
"automd": "^0.4.0",
|
||||
"changelogen": "^0.6.2",
|
||||
"eslint": "^9.34.0",
|
||||
"eslint-config-unjs": "^0.5.0",
|
||||
"in-range": "^3.0.0",
|
||||
"obuild": "^0.2.1",
|
||||
"prettier": "^3.6.2",
|
||||
"time-span": "^5.1.0",
|
||||
"typescript": "^5.9.2",
|
||||
"vitest": "^3.2.4"
|
||||
},
|
||||
"packageManager": "[email protected]"
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"name": "@nuxt/vite-builder",
|
||||
"version": "3.19.2",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/nuxt/nuxt.git",
|
||||
"directory": "packages/vite"
|
||||
},
|
||||
"description": "Vite bundler for Nuxt",
|
||||
"homepage": "https://nuxt.com",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.mjs"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"devDependencies": {
|
||||
"rollup": "4.50.1",
|
||||
"unbuild": "3.6.1",
|
||||
"vue": "3.5.21",
|
||||
"@nuxt/schema": "3.19.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@rollup/plugin-replace": "^6.0.2",
|
||||
"@vitejs/plugin-vue": "^6.0.1",
|
||||
"@vitejs/plugin-vue-jsx": "^5.1.1",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"consola": "^3.4.2",
|
||||
"cssnano": "^7.1.1",
|
||||
"defu": "^6.1.4",
|
||||
"esbuild": "^0.25.9",
|
||||
"escape-string-regexp": "^5.0.0",
|
||||
"exsolve": "^1.0.7",
|
||||
"externality": "^1.0.2",
|
||||
"get-port-please": "^3.2.0",
|
||||
"h3": "^1.15.4",
|
||||
"jiti": "^2.5.1",
|
||||
"knitwork": "^1.2.0",
|
||||
"magic-string": "^0.30.19",
|
||||
"mlly": "^1.8.0",
|
||||
"mocked-exports": "^0.1.1",
|
||||
"ohash": "^2.0.11",
|
||||
"pathe": "^2.0.3",
|
||||
"perfect-debounce": "^2.0.0",
|
||||
"pkg-types": "^2.3.0",
|
||||
"postcss": "^8.5.6",
|
||||
"rollup-plugin-visualizer": "^6.0.3",
|
||||
"std-env": "^3.9.0",
|
||||
"ufo": "^1.6.1",
|
||||
"unenv": "^2.0.0-rc.21",
|
||||
"vite": "^7.1.5",
|
||||
"vite-node": "^3.2.4",
|
||||
"vite-plugin-checker": "^0.10.3",
|
||||
"vue-bundle-renderer": "^2.1.2",
|
||||
"@nuxt/kit": "3.19.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^3.3.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
},
|
||||
"scripts": {
|
||||
"test:attw": "attw --pack"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user