penambahan web socket

This commit is contained in:
2025-09-18 19:01:22 +07:00
parent 1d053646a9
commit d7bb2eb5bb
15070 changed files with 2402916 additions and 0 deletions

No files matched your search

@@ -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.
@@ -0,0 +1,117 @@
[![Nuxt banner](https://github.com/nuxt/nuxt/tree/main/.github/assets/banner.svg)](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. Its 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>&nbsp;&nbsp;<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>&nbsp;&nbsp;<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>&nbsp;&nbsp;<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)
@@ -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 };
@@ -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 };
@@ -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 };
@@ -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 };
@@ -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';
@@ -0,0 +1,2 @@
declare function _default(): Promise<import("vue-bundle-renderer").Manifest>;
export default _default;
@@ -0,0 +1,4 @@
// @ts-check
import { viteNodeFetch } from './vite-node-shared.mjs'
export default () => viteNodeFetch.getManifest()
@@ -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;
@@ -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)
}
@@ -0,0 +1,2 @@
declare function _default(ssrContext: any): Promise<any>;
export default _default;
@@ -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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')}</pre><br>`,
].filter(Boolean).join(' ')
const stack = [
message,
`at ${loc}`,
errorData.stack,
].filter(Boolean).join('\n')
return {
message,
stack,
}
}
File diff suppressed because it is too large Load diff
@@ -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.
@@ -0,0 +1,119 @@
# perfect-debounce
<!-- automd:badges color=yellow codecov bundlephobia packagephobia -->
[![npm version](https://img.shields.io/npm/v/perfect-debounce?color=yellow)](https://npmjs.com/package/perfect-debounce)
[![npm downloads](https://img.shields.io/npm/dm/perfect-debounce?color=yellow)](https://npm.chart.dev/perfect-debounce)
[![bundle size](https://img.shields.io/bundlephobia/minzip/perfect-debounce?color=yellow)](https://bundlephobia.com/package/perfect-debounce)
[![install size](https://badgen.net/packagephobia/install/perfect-debounce?color=yellow)](https://packagephobia.com/result?p=perfect-debounce)
[![codecov](https://img.shields.io/codecov/c/gh/unjs/perfect-debounce?color=yellow)](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).
@@ -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 };
@@ -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 };
@@ -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]"
}
@@ -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"
}
}