FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

feat(vite): framework registration API by NathanWalker · Pull Request #11358 · NativeScript/NativeScript · GitHub

Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension .json  (1) .md  (2) .ts  (25) All 3 file types selected
Only manifest files
Viewed files
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Unified
Split
Hide whitespace
Diff view
Unified
Split
Hide whitespace
18 changes: 17 additions & 1 deletion packages/vite/README.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ npx nativescript-vite init

This will:

- Generate a `vite.config.mts` using the detected project flavor (Angular, Vue, React, Solid, TypeScript, or JavaScript) and the corresponding helper subpath from `@nativescript/vite`.
- Generate a `vite.config.mts` using the detected project flavor (Angular, Vue, React, Solid, TypeScript, or JavaScript — or a flavor a dependency declares, see below) and the corresponding helper subpath from `@nativescript/vite`.
- Add the dependency `@valor/nativescript-websockets`.
- Append `.ns-vite-build` to `.gitignore` if it is not already present.

Expand Down Expand Up @@ -152,6 +152,22 @@ import { solidConfig } from '@nativescript/vite/solid';
import { vueConfig } from '@nativescript/vite/vue';
```

### Flavors from other packages

A framework can ship its own flavor — config helper, server strategy and device-side
client strategy — as a package, using `@nativescript/vite/framework` and
`@nativescript/vite/hmr/client/framework.js`. `init` and flavor detection pick it up from a
`nativescript.vite` declaration in that package's `package.json`. The Octane flavor,
[`@nativescript-community/vite-octane`](https://github.com/nativescript-community/octane), is built this way:

```ts
import { octaneConfig } from '@nativescript-community/vite-octane';

export default defineConfig(({ mode }) => octaneConfig({ mode }));
```

See [docs/framework-flavors.md](./docs/framework-flavors.md) for the full walkthrough.

2) Update `nativescript.config.ts`:

```ts
Expand Down
2 changes: 1 addition & 1 deletion packages/vite/configuration/base.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ export const baseConfig = ({ mode, flavor }: { mode: string; flavor?: string }):
}

// Filtered logger to suppress noisy warnings
const filteredLogger = createFilteredViteLogger();
const filteredLogger = createFilteredViteLogger({ hmrActive });

// Create TypeScript aliases with platform support
const tsConfig = getTsConfigData({ platform, verbose });
Expand Down
241 changes: 241 additions & 0 deletions packages/vite/docs/framework-flavors.md

Large diffs are not rendered by default.

25 changes: 25 additions & 0 deletions packages/vite/framework.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* `@nativescript/vite/framework` — the surface a framework package uses to
* ship its own NativeScript HMR flavor (dev-server side, Node).
*
* A flavor is a name, a server strategy, and a client strategy module. The
* server strategy runs in the Vite process; the client strategy is fetched by
* the device next to the shared HMR client and is authored against
* `@nativescript/vite/hmr/client/framework.js`.
*/
export { registerFrameworkFlavor, getFrameworkFlavor, getClientStrategyDevicePath, isBuiltInFlavor } from './hmr/framework-flavors.js';
export type { FrameworkFlavorDefinition } from './hmr/framework-flavors.js';

export type { FrameworkServerStrategy, FrameworkProcessFileContext, FrameworkRegistryContext, FrameworkServedModuleContext, FrameworkModuleRequestContext, FrameworkRouteContext } from './hmr/server/framework-strategy.js';
export type { FrameworkClientStrategy, FrameworkClientBatchContext, FrameworkClientMessageContext, FrameworkClientMountContext, ClientGraphModule } from './hmr/client/framework-client-strategy.js';

/** The generic device-module pipeline; the usual base for a new server strategy. */
export { typescriptServerStrategy } from './hmr/frameworks/typescript/server/strategy.js';
/** Shared hot-update prologue every server strategy's `handleHotUpdate` starts with. */
export { runHotUpdatePrologue } from './hmr/server/websocket-hot-update.js';
export type { NsHotUpdateContext, HotUpdatePrologueState, HmrUpdateMetrics } from './hmr/server/websocket-hot-update.js';
export { purgeTransformCachesForHotUpdate } from './hmr/server/transform-cache-invalidation.js';

export { baseConfig } from './configuration/base.js';
export { getTypeCheckPlugins } from './helpers/typescript-check.js';
export type { TypeCheckControlOptions, TypeCheckSetting, TypeCheckFlavor } from './helpers/typescript-check.js';
50 changes: 49 additions & 1 deletion packages/vite/helpers/flavor.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -1,5 +1,48 @@
// import { defaultConfigs } from '..';
import { existsSync, readFileSync } from 'node:fs';
import * as path from 'node:path';
import { getAllDependencies } from './utils.js';
import { findMonorepoWorkspaceRoot, getProjectRootPath } from './project.js';

/**
* A flavor declared by a framework package in its own package.json:
*
* "nativescript": { "vite": { "flavor": "octane", "config": { "import": "octaneConfig", "from": "@nativescript-community/vite-octane" } } }
*
* The dependency that carries it identifies the flavor for detection, and
* `config` tells `nativescript-vite init` which helper to scaffold.
*/
export interface DeclaredViteFlavor {
flavor: string;
package: string;
config?: { import: string; from: string };
}

function readDeclaredViteFlavor(dependency: string): DeclaredViteFlavor | null {
const projectRoot = getProjectRootPath();
const roots = [projectRoot, findMonorepoWorkspaceRoot(projectRoot)].filter((root): root is string => !!root);
for (const root of roots) {
const manifest = path.join(root, 'node_modules', dependency, 'package.json');
if (!existsSync(manifest)) continue;
try {
const vite = JSON.parse(readFileSync(manifest, 'utf8'))?.nativescript?.vite;
if (vite && typeof vite.flavor === 'string' && vite.flavor) {
const config = vite.config && typeof vite.config.import === 'string' && typeof vite.config.from === 'string' ? { import: vite.config.import, from: vite.config.from } : undefined;
return { flavor: vite.flavor, package: dependency, config };
}
} catch {}
return null;
}
return null;
}

/** The first installed dependency that declares a Vite flavor, if any. */
export function findDeclaredViteFlavor(): DeclaredViteFlavor | null {
for (const dependency of getAllDependencies()) {
const declared = readDeclaredViteFlavor(dependency);
if (declared) return declared;
}
return null;
}

let targetFlavor: string;

Expand Down Expand Up @@ -59,6 +102,11 @@ export function determineProjectFlavor(): string | false {
return 'svelte';
}

const declared = findDeclaredViteFlavor();
if (declared) {
return declared.flavor;
}

// the order is important - angular, react, and svelte also include these deps
// but should return prior to this condition!
if (dependencies.includes('@nativescript/core') && dependencies.includes('typescript')) {
Expand Down
5 changes: 5 additions & 0 deletions packages/vite/helpers/global-defines.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { getProjectAppPath, getProjectAppVirtualPath } from './utils.js';
import { getClientStrategyDevicePath } from '../hmr/framework-flavors.js';

const APP_ROOT_DIR = getProjectAppPath();
const APP_ROOT_VIRTUAL = getProjectAppVirtualPath();
Expand Down Expand Up @@ -93,6 +94,9 @@ export function getRuntimeSeedValues(opts: { platform?: string; isDevMode: boole
isIOS: values.__APPLE__,
// Runtime flavor for the raw-served HMR client's TARGET_FLAVOR resolution.
__NS_TARGET_FLAVOR__: opts.flavor,
// Device path of a registered (non built-in) flavor's client strategy;
// '' for built-ins, which the client resolves from its own package.
__NS_CLIENT_STRATEGY_URL__: getClientStrategyDevicePath(opts.flavor),
// App-root virtual path — every served-id → moduleName mapping (frame
// navigation targets, modal re-present matching) depends on this.
__NS_APP_ROOT_DIR__: APP_ROOT_DIR,
Expand Down Expand Up @@ -247,6 +251,7 @@ export function getGlobalDefines(opts: { platform: string; targetMode: string; v
__non_webpack_require__: 'globalThis.require',
__NS_ENV_VERBOSE__: JSON.stringify(values.__NS_ENV_VERBOSE__),
__NS_TARGET_FLAVOR__: JSON.stringify(opts.flavor),
__NS_CLIENT_STRATEGY_URL__: JSON.stringify(getClientStrategyDevicePath(opts.flavor)),
// whether to show the HMR in-progress overlay.
__NS_HMR_PROGRESS_OVERLAY_ENABLED__: JSON.stringify(isHmrProgressOverlayEnabled()),
__CSS_PARSER__: JSON.stringify(values.__CSS_PARSER__),
Expand Down
25 changes: 19 additions & 6 deletions packages/vite/helpers/init.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import fs from 'fs';
import path from 'path';
import { createRequire } from 'node:module';
import { determineProjectFlavor } from './flavor.js';
import { determineProjectFlavor, findDeclaredViteFlavor } from './flavor.js';
import { getProjectFilePath, getProjectRootPath } from './project.js';

const require = createRequire(import.meta.url);
Expand Down Expand Up @@ -90,14 +90,27 @@ function getFlavorImportAndConfig(flavor: string): { importLine: string; configE
configExpr: 'typescriptConfig({ mode })',
};
case 'javascript':
default:
return {
importLine: "import { javascriptConfig } from '@nativescript/vite/javascript';",
configExpr: 'javascriptConfig({ mode })',
};
return javascriptImportAndConfig();
default: {
const declared = findDeclaredViteFlavor();
if (declared?.flavor === flavor && declared.config) {
return {
importLine: `import { ${declared.config.import} } from '${declared.config.from}';`,
configExpr: `${declared.config.import}({ mode })`,
};
}
return javascriptImportAndConfig();
}
}
}

function javascriptImportAndConfig(): { importLine: string; configExpr: string } {
return {
importLine: "import { javascriptConfig } from '@nativescript/vite/javascript';",
configExpr: 'javascriptConfig({ mode })',
};
}

function ensureViteConfig() {
const root = getProjectRootPath();
const existing = ['vite.config.mts', 'vite.config.ts', 'vite.config.mjs', 'vite.config.js', 'vite.config.cts', 'vite.config.cjs'].find((name) => fs.existsSync(path.join(root, name)));
Expand Down
10 changes: 10 additions & 0 deletions packages/vite/helpers/logging.spec.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -162,3 +162,13 @@ describe('shouldSuppressViteWarning', () => {
});
});
});

describe('shouldSuppressViteInfo', () => {
it('drops the stock web-client HMR verdicts, which never apply to a device session', async () => {
const { shouldSuppressViteInfo } = await import('./logging.js');
expect(shouldSuppressViteInfo('page reload src/octane/driver.ts')).toBe(true);
expect(shouldSuppressViteInfo('hmr update /src/app.tsx')).toBe(true);
expect(shouldSuppressViteInfo(' VITE v8.2.2 ready in 1007 ms')).toBe(false);
expect(shouldSuppressViteInfo('server restarted.')).toBe(false);
});
});
17 changes: 16 additions & 1 deletion packages/vite/helpers/logging.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,14 @@ export function clearVerboseCache(): void {
// All matching uses `.includes()` (never `.startsWith()`) because Vite wraps
// some warnings in picocolors ANSI escape sequences before handing them to
// the logger, which would defeat `startsWith`-style probes on TTY output.
export function createFilteredViteLogger(): Logger {
export function createFilteredViteLogger(options: { hmrActive?: boolean } = {}): Logger {
const baseLogger = createLogger(undefined, { allowClearScreen: true });
return {
...baseLogger,
info(message: any, opts?: any) {
if (options.hmrActive && shouldSuppressViteInfo(String(message || ''))) return;
return baseLogger.info(message, opts);
},
warn(message: any, options?: any) {
const msg = String(message || '');
if (shouldSuppressViteWarning(msg)) return;
Expand All @@ -121,6 +125,17 @@ export function createFilteredViteLogger(): Logger {
};
}

/**
* Vite's stock HMR client never connects under device HMR — the device talks
* to `/ns-hmr` — so Vite's own verdicts about that client are noise, and one
* of them misleads: `page reload <file>` is what Vite decides for any module
* it cannot hot-accept on the web, printed while the device is applying the
* same save in place through a framework strategy.
*/
export function shouldSuppressViteInfo(msg: string): boolean {
return /\bpage reload\b/.test(msg) || /\bhmr update\b/.test(msg);
}

// Exported for unit tests. Keep this function pure so the test suite can
// exercise every suppression pattern without instantiating a real logger.
export function shouldSuppressViteWarning(msg: string): boolean {
Expand Down
2 changes: 1 addition & 1 deletion packages/vite/helpers/typescript-check.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import type { Platform } from './platform-types.js';
const require = createRequire(import.meta.url);

export type PlatformType = Platform;
type TypeCheckFlavor = 'typescript' | 'react' | 'solid' | 'vue' | 'angular' | 'javascript';
export type TypeCheckFlavor = 'typescript' | 'react' | 'solid' | 'vue' | 'angular' | 'javascript';

export type TypeCheckMode = 'off' | 'warn' | 'error';

Expand Down
34 changes: 34 additions & 0 deletions packages/vite/hmr/client/framework-client-strategy.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,40 @@ export interface FrameworkClientStrategy {
/** Record changed-module metadata from a delta payload (framework bookkeeping). */
recordPayloadChanges?(changed: any[], graphVersion: number): void;

/**
* Decide whether a changed module goes through the shared evict + re-import
* queue in this realm. Return `false` for a module whose fresh body must
* not evaluate here — a worker script only a worker isolate ever loaded,
* a type-only module nothing imports. Declined ids are handed to
* {@link applyUnqueuedChanges} instead. Defaults to `true`.
*/
shouldQueueReimport?(id: string): boolean;

/**
* Apply the changed modules {@link shouldQueueReimport} declined, in the
* same delta. Runs only after boot, for real edits, and only when at least
* one id was declined.
*/
applyUnqueuedChanges?(ids: string[]): void | Promise<void>;

/**
* A full graph arrived after boot with hashes that differ from the client's
* mirror — a reconnect after a dev-server restart, or version drift. The
* shared fallback re-imports every changed module piecemeal, outside any
* accept/dispose sequencing. Return `true` to take over (Octane requests
* one ordered graph reload instead); `changedIds` is the inferred set.
*/
handleGraphResync?(changedIds: string[]): boolean | Promise<boolean>;

/**
* Runs once per queue drain, BEFORE the changed modules are evicted from
* the runtime registry. The only point at which a framework can still reach
* the module instances about to be replaced — their `hot.accept` callbacks
* and `hot.dispose` registrations are reset when the fresh bodies evaluate
* (Octane: Vite-parity accept/dispose sequencing).
*/
beforeBatchEvict?(drained: string[]): void;

/**
* Post-process one freshly re-imported module during the queue drain
* (TypeScript/React: refresh the bundled module registry so Builder /
Expand Down
36 changes: 36 additions & 0 deletions packages/vite/hmr/client/framework.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* `@nativescript/vite/hmr/client/framework.js` — the device-side surface a
* framework's client strategy is written against.
*
* A registered flavor's client module is served to the device through
* `/ns/m` and evaluates in the same realm as the shared HMR client. It must
* reach the client's singletons — the module graph mirror, the hot registry,
* the overlay — through this one module, so that both resolve to the same
* canonical URLs and therefore the same instances. Importing the client's
* internal files by path is not supported.
*/
export type { FrameworkClientStrategy, FrameworkClientBatchContext, FrameworkClientMessageContext, FrameworkClientMountContext, ClientGraphModule } from './framework-client-strategy.js';
export type { NsHotRegistry, NsHotContext } from './hot-context.js';

/** The process-wide `import.meta.hot` registry: accept/dispose callbacks, dependency acceptors, `hot.data`, events, full reload. */
export { getNsHotRegistry } from './hot-context.js';

/** Live mirror of the server's module graph (id → { deps, hash }). */
export { graph, getGraphVersion } from './utils.js';
/** Canonical-URL helpers and the evict + re-import primitives the shared queue uses. */
export { normalizeSpec, requestModuleFromServer, invalidateModulesByUrls, buildEvictionUrls, resolveHmrHttpOrigin, safeDynImport } from './utils.js';
/** `@nativescript/core` export lookup that resolves against the live core realm. */
export { getCore } from './utils.js';
export { ENV_VERBOSE } from './utils.js';

/** Drive the on-device "HMR update" overlay: 'received' | 'evicting' | 'reimporting' | 'rebooting' | 'complete'. */
export { setUpdateStage, getOverlayApi } from './overlay-driver.js';
export type { HmrUpdateOverlayStage } from './overlay-driver.js';

/** Swap the live root view for a freshly loaded component (the reset path). */
export { performResetRoot } from './root-reset.js';

export { getGlobalScope } from '../shared/runtime/global-scope.js';
/** The `ns:module` builtin as the runtime exposes it (`invalidateModules`, `getLoadedModuleUrls`, …); `{}` off-device. */
export { readNsRuntimeDevHostApi } from '../shared/runtime/browser-runtime-contract.js';
export type { NsRuntimeDevHostApi } from '../shared/runtime/browser-runtime-contract.js';
Loading
Loading

Back | FazBrowse Home | New Git URL