| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -44,7 +44,7 @@ npx nativescript-vite init | |||
| 44 | 44 | ||
| 45 | 45 | This will: | |
| 46 | 46 | ||
| 47 | - - 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`. | ||
| 47 | + - 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`. | ||
| 48 | 48 | - Add the dependency `@valor/nativescript-websockets`. | |
| 49 | 49 | - Append `.ns-vite-build` to `.gitignore` if it is not already present. | |
| 50 | 50 | ||
@@ -152,6 +152,22 @@ import { solidConfig } from '@nativescript/vite/solid'; | |||
| 152 | 152 | import { vueConfig } from '@nativescript/vite/vue'; | |
| 153 | 153 | ``` | |
| 154 | 154 | ||
| 155 | + ### Flavors from other packages | ||
| 156 | + | ||
| 157 | + A framework can ship its own flavor — config helper, server strategy and device-side | ||
| 158 | + client strategy — as a package, using `@nativescript/vite/framework` and | ||
| 159 | + `@nativescript/vite/hmr/client/framework.js`. `init` and flavor detection pick it up from a | ||
| 160 | + `nativescript.vite` declaration in that package's `package.json`. The Octane flavor, | ||
| 161 | + [`@nativescript-community/vite-octane`](https://github.com/nativescript-community/octane), is built this way: | ||
| 162 | + | ||
| 163 | + ```ts | ||
| 164 | + import { octaneConfig } from '@nativescript-community/vite-octane'; | ||
| 165 | + | ||
| 166 | + export default defineConfig(({ mode }) => octaneConfig({ mode })); | ||
| 167 | + ``` | ||
| 168 | + | ||
| 169 | + See [docs/framework-flavors.md](./docs/framework-flavors.md) for the full walkthrough. | ||
| 170 | + | ||
| 155 | 171 | 2) Update `nativescript.config.ts`: | |
| 156 | 172 | ||
| 157 | 173 | ```ts | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -120,7 +120,7 @@ export const baseConfig = ({ mode, flavor }: { mode: string; flavor?: string }): | |||
| 120 | 120 | } | |
| 121 | 121 | ||
| 122 | 122 | // Filtered logger to suppress noisy warnings | |
| 123 | - const filteredLogger = createFilteredViteLogger(); | ||
| 123 | + const filteredLogger = createFilteredViteLogger({ hmrActive }); | ||
| 124 | 124 | ||
| 125 | 125 | // Create TypeScript aliases with platform support | |
| 126 | 126 | const tsConfig = getTsConfigData({ platform, verbose }); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,25 @@ | |||
| 1 | + /** | ||
| 2 | + * `@nativescript/vite/framework` — the surface a framework package uses to | ||
| 3 | + * ship its own NativeScript HMR flavor (dev-server side, Node). | ||
| 4 | + * | ||
| 5 | + * A flavor is a name, a server strategy, and a client strategy module. The | ||
| 6 | + * server strategy runs in the Vite process; the client strategy is fetched by | ||
| 7 | + * the device next to the shared HMR client and is authored against | ||
| 8 | + * `@nativescript/vite/hmr/client/framework.js`. | ||
| 9 | + */ | ||
| 10 | + export { registerFrameworkFlavor, getFrameworkFlavor, getClientStrategyDevicePath, isBuiltInFlavor } from './hmr/framework-flavors.js'; | ||
| 11 | + export type { FrameworkFlavorDefinition } from './hmr/framework-flavors.js'; | ||
| 12 | + | ||
| 13 | + export type { FrameworkServerStrategy, FrameworkProcessFileContext, FrameworkRegistryContext, FrameworkServedModuleContext, FrameworkModuleRequestContext, FrameworkRouteContext } from './hmr/server/framework-strategy.js'; | ||
| 14 | + export type { FrameworkClientStrategy, FrameworkClientBatchContext, FrameworkClientMessageContext, FrameworkClientMountContext, ClientGraphModule } from './hmr/client/framework-client-strategy.js'; | ||
| 15 | + | ||
| 16 | + /** The generic device-module pipeline; the usual base for a new server strategy. */ | ||
| 17 | + export { typescriptServerStrategy } from './hmr/frameworks/typescript/server/strategy.js'; | ||
| 18 | + /** Shared hot-update prologue every server strategy's `handleHotUpdate` starts with. */ | ||
| 19 | + export { runHotUpdatePrologue } from './hmr/server/websocket-hot-update.js'; | ||
| 20 | + export type { NsHotUpdateContext, HotUpdatePrologueState, HmrUpdateMetrics } from './hmr/server/websocket-hot-update.js'; | ||
| 21 | + export { purgeTransformCachesForHotUpdate } from './hmr/server/transform-cache-invalidation.js'; | ||
| 22 | + | ||
| 23 | + export { baseConfig } from './configuration/base.js'; | ||
| 24 | + export { getTypeCheckPlugins } from './helpers/typescript-check.js'; | ||
| 25 | + export type { TypeCheckControlOptions, TypeCheckSetting, TypeCheckFlavor } from './helpers/typescript-check.js'; | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,5 +1,48 @@ | |||
| 1 | - // import { defaultConfigs } from '..'; | ||
| 1 | + import { existsSync, readFileSync } from 'node:fs'; | ||
| 2 | + import * as path from 'node:path'; | ||
| 2 | 3 | import { getAllDependencies } from './utils.js'; | |
| 4 | + import { findMonorepoWorkspaceRoot, getProjectRootPath } from './project.js'; | ||
| 5 | + | ||
| 6 | + /** | ||
| 7 | + * A flavor declared by a framework package in its own package.json: | ||
| 8 | + * | ||
| 9 | + * "nativescript": { "vite": { "flavor": "octane", "config": { "import": "octaneConfig", "from": "@nativescript-community/vite-octane" } } } | ||
| 10 | + * | ||
| 11 | + * The dependency that carries it identifies the flavor for detection, and | ||
| 12 | + * `config` tells `nativescript-vite init` which helper to scaffold. | ||
| 13 | + */ | ||
| 14 | + export interface DeclaredViteFlavor { | ||
| 15 | + flavor: string; | ||
| 16 | + package: string; | ||
| 17 | + config?: { import: string; from: string }; | ||
| 18 | + } | ||
| 19 | + | ||
| 20 | + function readDeclaredViteFlavor(dependency: string): DeclaredViteFlavor | null { | ||
| 21 | + const projectRoot = getProjectRootPath(); | ||
| 22 | + const roots = [projectRoot, findMonorepoWorkspaceRoot(projectRoot)].filter((root): root is string => !!root); | ||
| 23 | + for (const root of roots) { | ||
| 24 | + const manifest = path.join(root, 'node_modules', dependency, 'package.json'); | ||
| 25 | + if (!existsSync(manifest)) continue; | ||
| 26 | + try { | ||
| 27 | + const vite = JSON.parse(readFileSync(manifest, 'utf8'))?.nativescript?.vite; | ||
| 28 | + if (vite && typeof vite.flavor === 'string' && vite.flavor) { | ||
| 29 | + const config = vite.config && typeof vite.config.import === 'string' && typeof vite.config.from === 'string' ? { import: vite.config.import, from: vite.config.from } : undefined; | ||
| 30 | + return { flavor: vite.flavor, package: dependency, config }; | ||
| 31 | + } | ||
| 32 | + } catch {} | ||
| 33 | + return null; | ||
| 34 | + } | ||
| 35 | + return null; | ||
| 36 | + } | ||
| 37 | + | ||
| 38 | + /** The first installed dependency that declares a Vite flavor, if any. */ | ||
| 39 | + export function findDeclaredViteFlavor(): DeclaredViteFlavor | null { | ||
| 40 | + for (const dependency of getAllDependencies()) { | ||
| 41 | + const declared = readDeclaredViteFlavor(dependency); | ||
| 42 | + if (declared) return declared; | ||
| 43 | + } | ||
| 44 | + return null; | ||
| 45 | + } | ||
| 3 | 46 | ||
| 4 | 47 | let targetFlavor: string; | |
| 5 | 48 | ||
@@ -59,6 +102,11 @@ export function determineProjectFlavor(): string | false { | |||
| 59 | 102 | return 'svelte'; | |
| 60 | 103 | } | |
| 61 | 104 | ||
| 105 | + const declared = findDeclaredViteFlavor(); | ||
| 106 | + if (declared) { | ||
| 107 | + return declared.flavor; | ||
| 108 | + } | ||
| 109 | + | ||
| 62 | 110 | // the order is important - angular, react, and svelte also include these deps | |
| 63 | 111 | // but should return prior to this condition! | |
| 64 | 112 | if (dependencies.includes('@nativescript/core') && dependencies.includes('typescript')) { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,4 +1,5 @@ | |||
| 1 | 1 | import { getProjectAppPath, getProjectAppVirtualPath } from './utils.js'; | |
| 2 | + import { getClientStrategyDevicePath } from '../hmr/framework-flavors.js'; | ||
| 2 | 3 | ||
| 3 | 4 | const APP_ROOT_DIR = getProjectAppPath(); | |
| 4 | 5 | const APP_ROOT_VIRTUAL = getProjectAppVirtualPath(); | |
@@ -93,6 +94,9 @@ export function getRuntimeSeedValues(opts: { platform?: string; isDevMode: boole | |||
| 93 | 94 | isIOS: values.__APPLE__, | |
| 94 | 95 | // Runtime flavor for the raw-served HMR client's TARGET_FLAVOR resolution. | |
| 95 | 96 | __NS_TARGET_FLAVOR__: opts.flavor, | |
| 97 | + // Device path of a registered (non built-in) flavor's client strategy; | ||
| 98 | + // '' for built-ins, which the client resolves from its own package. | ||
| 99 | + __NS_CLIENT_STRATEGY_URL__: getClientStrategyDevicePath(opts.flavor), | ||
| 96 | 100 | // App-root virtual path — every served-id → moduleName mapping (frame | |
| 97 | 101 | // navigation targets, modal re-present matching) depends on this. | |
| 98 | 102 | __NS_APP_ROOT_DIR__: APP_ROOT_DIR, | |
@@ -247,6 +251,7 @@ export function getGlobalDefines(opts: { platform: string; targetMode: string; v | |||
| 247 | 251 | __non_webpack_require__: 'globalThis.require', | |
| 248 | 252 | __NS_ENV_VERBOSE__: JSON.stringify(values.__NS_ENV_VERBOSE__), | |
| 249 | 253 | __NS_TARGET_FLAVOR__: JSON.stringify(opts.flavor), | |
| 254 | + __NS_CLIENT_STRATEGY_URL__: JSON.stringify(getClientStrategyDevicePath(opts.flavor)), | ||
| 250 | 255 | // whether to show the HMR in-progress overlay. | |
| 251 | 256 | __NS_HMR_PROGRESS_OVERLAY_ENABLED__: JSON.stringify(isHmrProgressOverlayEnabled()), | |
| 252 | 257 | __CSS_PARSER__: JSON.stringify(values.__CSS_PARSER__), | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,7 +1,7 @@ | |||
| 1 | 1 | import fs from 'fs'; | |
| 2 | 2 | import path from 'path'; | |
| 3 | 3 | import { createRequire } from 'node:module'; | |
| 4 | - import { determineProjectFlavor } from './flavor.js'; | ||
| 4 | + import { determineProjectFlavor, findDeclaredViteFlavor } from './flavor.js'; | ||
| 5 | 5 | import { getProjectFilePath, getProjectRootPath } from './project.js'; | |
| 6 | 6 | ||
| 7 | 7 | const require = createRequire(import.meta.url); | |
@@ -90,14 +90,27 @@ function getFlavorImportAndConfig(flavor: string): { importLine: string; configE | |||
| 90 | 90 | configExpr: 'typescriptConfig({ mode })', | |
| 91 | 91 | }; | |
| 92 | 92 | case 'javascript': | |
| 93 | - default: | ||
| 94 | - return { | ||
| 95 | - importLine: "import { javascriptConfig } from '@nativescript/vite/javascript';", | ||
| 96 | - configExpr: 'javascriptConfig({ mode })', | ||
| 97 | - }; | ||
| 93 | + return javascriptImportAndConfig(); | ||
| 94 | + default: { | ||
| 95 | + const declared = findDeclaredViteFlavor(); | ||
| 96 | + if (declared?.flavor === flavor && declared.config) { | ||
| 97 | + return { | ||
| 98 | + importLine: `import { ${declared.config.import} } from '${declared.config.from}';`, | ||
| 99 | + configExpr: `${declared.config.import}({ mode })`, | ||
| 100 | + }; | ||
| 101 | + } | ||
| 102 | + return javascriptImportAndConfig(); | ||
| 103 | + } | ||
| 98 | 104 | } | |
| 99 | 105 | } | |
| 100 | 106 | ||
| 107 | + function javascriptImportAndConfig(): { importLine: string; configExpr: string } { | ||
| 108 | + return { | ||
| 109 | + importLine: "import { javascriptConfig } from '@nativescript/vite/javascript';", | ||
| 110 | + configExpr: 'javascriptConfig({ mode })', | ||
| 111 | + }; | ||
| 112 | + } | ||
| 113 | + | ||
| 101 | 114 | function ensureViteConfig() { | |
| 102 | 115 | const root = getProjectRootPath(); | |
| 103 | 116 | 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))); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -162,3 +162,13 @@ describe('shouldSuppressViteWarning', () => { | |||
| 162 | 162 | }); | |
| 163 | 163 | }); | |
| 164 | 164 | }); | |
| 165 | + | ||
| 166 | + describe('shouldSuppressViteInfo', () => { | ||
| 167 | + it('drops the stock web-client HMR verdicts, which never apply to a device session', async () => { | ||
| 168 | + const { shouldSuppressViteInfo } = await import('./logging.js'); | ||
| 169 | + expect(shouldSuppressViteInfo('page reload src/octane/driver.ts')).toBe(true); | ||
| 170 | + expect(shouldSuppressViteInfo('hmr update /src/app.tsx')).toBe(true); | ||
| 171 | + expect(shouldSuppressViteInfo(' VITE v8.2.2 ready in 1007 ms')).toBe(false); | ||
| 172 | + expect(shouldSuppressViteInfo('server restarted.')).toBe(false); | ||
| 173 | + }); | ||
| 174 | + }); | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -104,10 +104,14 @@ export function clearVerboseCache(): void { | |||
| 104 | 104 | // All matching uses `.includes()` (never `.startsWith()`) because Vite wraps | |
| 105 | 105 | // some warnings in picocolors ANSI escape sequences before handing them to | |
| 106 | 106 | // the logger, which would defeat `startsWith`-style probes on TTY output. | |
| 107 | - export function createFilteredViteLogger(): Logger { | ||
| 107 | + export function createFilteredViteLogger(options: { hmrActive?: boolean } = {}): Logger { | ||
| 108 | 108 | const baseLogger = createLogger(undefined, { allowClearScreen: true }); | |
| 109 | 109 | return { | |
| 110 | 110 | ...baseLogger, | |
| 111 | + info(message: any, opts?: any) { | ||
| 112 | + if (options.hmrActive && shouldSuppressViteInfo(String(message || ''))) return; | ||
| 113 | + return baseLogger.info(message, opts); | ||
| 114 | + }, | ||
| 111 | 115 | warn(message: any, options?: any) { | |
| 112 | 116 | const msg = String(message || ''); | |
| 113 | 117 | if (shouldSuppressViteWarning(msg)) return; | |
@@ -121,6 +125,17 @@ export function createFilteredViteLogger(): Logger { | |||
| 121 | 125 | }; | |
| 122 | 126 | } | |
| 123 | 127 | ||
| 128 | + /** | ||
| 129 | + * Vite's stock HMR client never connects under device HMR — the device talks | ||
| 130 | + * to `/ns-hmr` — so Vite's own verdicts about that client are noise, and one | ||
| 131 | + * of them misleads: `page reload <file>` is what Vite decides for any module | ||
| 132 | + * it cannot hot-accept on the web, printed while the device is applying the | ||
| 133 | + * same save in place through a framework strategy. | ||
| 134 | + */ | ||
| 135 | + export function shouldSuppressViteInfo(msg: string): boolean { | ||
| 136 | + return /\bpage reload\b/.test(msg) || /\bhmr update\b/.test(msg); | ||
| 137 | + } | ||
| 138 | + | ||
| 124 | 139 | // Exported for unit tests. Keep this function pure so the test suite can | |
| 125 | 140 | // exercise every suppression pattern without instantiating a real logger. | |
| 126 | 141 | export function shouldSuppressViteWarning(msg: string): boolean { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -10,7 +10,7 @@ import type { Platform } from './platform-types.js'; | |||
| 10 | 10 | const require = createRequire(import.meta.url); | |
| 11 | 11 | ||
| 12 | 12 | export type PlatformType = Platform; | |
| 13 | - type TypeCheckFlavor = 'typescript' | 'react' | 'solid' | 'vue' | 'angular' | 'javascript'; | ||
| 13 | + export type TypeCheckFlavor = 'typescript' | 'react' | 'solid' | 'vue' | 'angular' | 'javascript'; | ||
| 14 | 14 | ||
| 15 | 15 | export type TypeCheckMode = 'off' | 'warn' | 'error'; | |
| 16 | 16 | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments