one 1.24.8 → 1.25.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/cli/build.cjs +10 -0
- package/dist/cjs/cli/build.native.js +107 -96
- package/dist/cjs/cli/build.native.js.map +1 -1
- package/dist/cjs/headless-server.cjs +1 -0
- package/dist/cjs/headless-server.native.js +5 -4
- package/dist/cjs/headless-server.native.js.map +1 -1
- package/dist/cjs/server/setupBuildOptions.cjs +8 -0
- package/dist/cjs/server/setupBuildOptions.native.js +30 -0
- package/dist/cjs/server/setupBuildOptions.native.js.map +1 -1
- package/dist/cjs/vite/one.cjs +77 -46
- package/dist/cjs/vite/one.native.js +129 -92
- package/dist/cjs/vite/one.native.js.map +1 -1
- package/dist/cjs/vite/plugins/bundledDevPlugin.cjs +16 -7
- package/dist/cjs/vite/plugins/bundledDevPlugin.native.js +16 -7
- package/dist/cjs/vite/plugins/bundledDevPlugin.native.js.map +1 -1
- package/dist/cjs/vite/plugins/criticalCSSPlugin.cjs +15 -11
- package/dist/cjs/vite/plugins/criticalCSSPlugin.native.js +15 -11
- package/dist/cjs/vite/plugins/criticalCSSPlugin.native.js.map +1 -1
- package/dist/cjs/vite/plugins/criticalCSSPlugin.test.cjs +9 -25
- package/dist/cjs/vite/plugins/criticalCSSPlugin.test.native.js +9 -25
- package/dist/cjs/vite/plugins/criticalCSSPlugin.test.native.js.map +1 -1
- package/dist/cjs/vite/plugins/environmentGuardPlugin.cjs +22 -7
- package/dist/cjs/vite/plugins/environmentGuardPlugin.native.js +23 -8
- package/dist/cjs/vite/plugins/environmentGuardPlugin.native.js.map +1 -1
- package/dist/cjs/vite/plugins/fileSystemRouterPlugin.cjs +1 -1
- package/dist/cjs/vite/plugins/fileSystemRouterPlugin.native.js +1 -1
- package/dist/cjs/vite/plugins/fileSystemRouterPlugin.native.js.map +1 -1
- package/dist/cjs/vite/plugins/imageDataPlugin.cjs +42 -32
- package/dist/cjs/vite/plugins/imageDataPlugin.native.js +42 -32
- package/dist/cjs/vite/plugins/imageDataPlugin.native.js.map +1 -1
- package/dist/cjs/vite/plugins/imageDataPlugin.test.cjs +18 -38
- package/dist/cjs/vite/plugins/imageDataPlugin.test.native.js +18 -38
- package/dist/cjs/vite/plugins/imageDataPlugin.test.native.js.map +1 -1
- package/dist/cjs/vite/plugins/virtualEntryPlugin.cjs +58 -43
- package/dist/cjs/vite/plugins/virtualEntryPlugin.native.js +93 -76
- package/dist/cjs/vite/plugins/virtualEntryPlugin.native.js.map +1 -1
- package/dist/cjs/vite/plugins/virtualEntryPlugin.test.cjs +1 -1
- package/dist/cjs/vite/plugins/virtualEntryPlugin.test.native.js +1 -1
- package/dist/cjs/vite/plugins/virtualEntryPlugin.test.native.js.map +1 -1
- package/dist/cjs/vite/plugins/warmRoutesPlugin.cjs +31 -7
- package/dist/cjs/vite/plugins/warmRoutesPlugin.native.js +35 -10
- package/dist/cjs/vite/plugins/warmRoutesPlugin.native.js.map +1 -1
- package/dist/esm/cli/build.mjs +10 -0
- package/dist/esm/cli/build.mjs.map +1 -1
- package/dist/esm/cli/build.native.js +107 -96
- package/dist/esm/cli/build.native.js.map +1 -1
- package/dist/esm/getURL.native.test.mjs +1 -1
- package/dist/esm/headless-server.mjs +1 -0
- package/dist/esm/headless-server.mjs.map +1 -1
- package/dist/esm/headless-server.native.js +5 -4
- package/dist/esm/headless-server.native.js.map +1 -1
- package/dist/esm/router/hmrImport.native.test.mjs +1 -1
- package/dist/esm/router/routeHmr.native.test.mjs +1 -1
- package/dist/esm/router/routeHmr.native.test.mjs.map +1 -1
- package/dist/esm/server/setupBuildOptions.mjs +8 -0
- package/dist/esm/server/setupBuildOptions.mjs.map +1 -1
- package/dist/esm/server/setupBuildOptions.native.js +30 -0
- package/dist/esm/server/setupBuildOptions.native.js.map +1 -1
- package/dist/esm/useLoader.native.js +0 -66
- package/dist/esm/useLoader.native.js.map +1 -1
- package/dist/esm/vite/one.mjs +77 -46
- package/dist/esm/vite/one.mjs.map +1 -1
- package/dist/esm/vite/one.native.js +129 -92
- package/dist/esm/vite/one.native.js.map +1 -1
- package/dist/esm/vite/plugins/bundledDevPlugin.mjs +16 -7
- package/dist/esm/vite/plugins/bundledDevPlugin.mjs.map +1 -1
- package/dist/esm/vite/plugins/bundledDevPlugin.native.js +16 -7
- package/dist/esm/vite/plugins/bundledDevPlugin.native.js.map +1 -1
- package/dist/esm/vite/plugins/criticalCSSPlugin.mjs +15 -11
- package/dist/esm/vite/plugins/criticalCSSPlugin.mjs.map +1 -1
- package/dist/esm/vite/plugins/criticalCSSPlugin.native.js +15 -11
- package/dist/esm/vite/plugins/criticalCSSPlugin.native.js.map +1 -1
- package/dist/esm/vite/plugins/criticalCSSPlugin.test.mjs +9 -25
- package/dist/esm/vite/plugins/criticalCSSPlugin.test.mjs.map +1 -1
- package/dist/esm/vite/plugins/criticalCSSPlugin.test.native.js +9 -25
- package/dist/esm/vite/plugins/criticalCSSPlugin.test.native.js.map +1 -1
- package/dist/esm/vite/plugins/environmentGuardPlugin.mjs +22 -7
- package/dist/esm/vite/plugins/environmentGuardPlugin.mjs.map +1 -1
- package/dist/esm/vite/plugins/environmentGuardPlugin.native.js +23 -8
- package/dist/esm/vite/plugins/environmentGuardPlugin.native.js.map +1 -1
- package/dist/esm/vite/plugins/fileSystemRouterPlugin.mjs +1 -1
- package/dist/esm/vite/plugins/fileSystemRouterPlugin.mjs.map +1 -1
- package/dist/esm/vite/plugins/fileSystemRouterPlugin.native.js +1 -1
- package/dist/esm/vite/plugins/fileSystemRouterPlugin.native.js.map +1 -1
- package/dist/esm/vite/plugins/imageDataPlugin.mjs +42 -32
- package/dist/esm/vite/plugins/imageDataPlugin.mjs.map +1 -1
- package/dist/esm/vite/plugins/imageDataPlugin.native.js +42 -32
- package/dist/esm/vite/plugins/imageDataPlugin.native.js.map +1 -1
- package/dist/esm/vite/plugins/imageDataPlugin.test.mjs +18 -38
- package/dist/esm/vite/plugins/imageDataPlugin.test.mjs.map +1 -1
- package/dist/esm/vite/plugins/imageDataPlugin.test.native.js +18 -38
- package/dist/esm/vite/plugins/imageDataPlugin.test.native.js.map +1 -1
- package/dist/esm/vite/plugins/virtualEntryPlugin.mjs +58 -43
- package/dist/esm/vite/plugins/virtualEntryPlugin.mjs.map +1 -1
- package/dist/esm/vite/plugins/virtualEntryPlugin.native.js +93 -76
- package/dist/esm/vite/plugins/virtualEntryPlugin.native.js.map +1 -1
- package/dist/esm/vite/plugins/virtualEntryPlugin.test.mjs +1 -1
- package/dist/esm/vite/plugins/virtualEntryPlugin.test.mjs.map +1 -1
- package/dist/esm/vite/plugins/virtualEntryPlugin.test.native.js +1 -1
- package/dist/esm/vite/plugins/virtualEntryPlugin.test.native.js.map +1 -1
- package/dist/esm/vite/plugins/warmRoutesPlugin.mjs +31 -7
- package/dist/esm/vite/plugins/warmRoutesPlugin.mjs.map +1 -1
- package/dist/esm/vite/plugins/warmRoutesPlugin.native.js +35 -10
- package/dist/esm/vite/plugins/warmRoutesPlugin.native.js.map +1 -1
- package/package.json +12 -12
- package/src/cli/build.ts +19 -0
- package/src/headless-server.ts +1 -0
- package/src/server/setupBuildOptions.ts +12 -0
- package/src/vite/one.ts +121 -66
- package/src/vite/plugins/bundledDevPlugin.ts +17 -7
- package/src/vite/plugins/criticalCSSPlugin.test.ts +11 -27
- package/src/vite/plugins/criticalCSSPlugin.ts +15 -11
- package/src/vite/plugins/environmentGuardPlugin.ts +20 -7
- package/src/vite/plugins/fileSystemRouterPlugin.tsx +7 -3
- package/src/vite/plugins/imageDataPlugin.test.ts +20 -39
- package/src/vite/plugins/imageDataPlugin.ts +49 -44
- package/src/vite/plugins/virtualEntryPlugin.test.ts +1 -1
- package/src/vite/plugins/virtualEntryPlugin.ts +139 -122
- package/src/vite/plugins/warmRoutesPlugin.ts +36 -10
- package/src/vite/types.ts +19 -2
- package/types/cli/build.d.ts.map +1 -1
- package/types/headless-server.d.ts.map +1 -1
- package/types/server/setupBuildOptions.d.ts.map +1 -1
- package/types/vite/one.d.ts.map +1 -1
- package/types/vite/plugins/bundledDevPlugin.d.ts.map +1 -1
- package/types/vite/plugins/criticalCSSPlugin.d.ts.map +1 -1
- package/types/vite/plugins/environmentGuardPlugin.d.ts.map +1 -1
- package/types/vite/plugins/fileSystemRouterPlugin.d.ts.map +1 -1
- package/types/vite/plugins/imageDataPlugin.d.ts.map +1 -1
- package/types/vite/plugins/virtualEntryPlugin.d.ts.map +1 -1
- package/types/vite/plugins/warmRoutesPlugin.d.ts.map +1 -1
- package/types/vite/types.d.ts +18 -2
- package/types/vite/types.d.ts.map +1 -1
package/src/vite/one.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
import events from 'node:events'
|
|
9
9
|
import { existsSync, readFileSync } from 'node:fs'
|
|
10
10
|
import path from 'node:path'
|
|
11
|
+
import { exactRegex } from 'rolldown/filter'
|
|
11
12
|
import { normalizePath, type Plugin, type PluginOption } from 'vite'
|
|
12
13
|
import { autoDepOptimizePlugin, getOptionsFilled, loadEnv } from 'vxrn'
|
|
13
14
|
import vxrnVitePlugin from 'vxrn/vite-plugin'
|
|
@@ -37,6 +38,11 @@ import type { One } from './types'
|
|
|
37
38
|
|
|
38
39
|
type MetroOptions = MetroPluginOptions
|
|
39
40
|
|
|
41
|
+
// deep react-native Libraries/* paths that don't exist in react-native-web.
|
|
42
|
+
// used as a rust-side resolveId filter so ordinary imports never reach js.
|
|
43
|
+
const RN_DEEP_IMPORT_RE =
|
|
44
|
+
/^react-native\/Libraries\/|react-native-web(-lite)?\/.*\/Libraries\//
|
|
45
|
+
|
|
40
46
|
/**
|
|
41
47
|
* This needs a big refactor!
|
|
42
48
|
* I guess these plugins are all being loaded by native??
|
|
@@ -388,6 +394,28 @@ export function one(options: One.PluginOptions = {}): PluginOption {
|
|
|
388
394
|
} catch {}
|
|
389
395
|
}
|
|
390
396
|
|
|
397
|
+
// load eagerly so the rust-side hook filter below can be built from
|
|
398
|
+
// the actual tsconfig prefixes. hook filters are fixed at
|
|
399
|
+
// registration time, so waiting for configResolved would mean no
|
|
400
|
+
// filter at all and a napi round trip for every import in the graph
|
|
401
|
+
loadMappings(root)
|
|
402
|
+
|
|
403
|
+
// only ids that could match a mapping reach js. if the eager load
|
|
404
|
+
// found nothing we can't narrow to prefixes, so fall back to "any
|
|
405
|
+
// bare specifier" — tsconfig paths are never relative or absolute,
|
|
406
|
+
// so this still drops every ./ and ../ import while staying correct
|
|
407
|
+
// if configResolved loads mappings later from a different root.
|
|
408
|
+
const tsconfigPathsFilter = mappings.length
|
|
409
|
+
? new RegExp(
|
|
410
|
+
mappings
|
|
411
|
+
.map(
|
|
412
|
+
(m) =>
|
|
413
|
+
`^${m.prefix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}${m.wildcard ? '' : '$'}`
|
|
414
|
+
)
|
|
415
|
+
.join('|')
|
|
416
|
+
)
|
|
417
|
+
: /^[^./]/
|
|
418
|
+
|
|
391
419
|
return {
|
|
392
420
|
name: 'one:tsconfig-paths',
|
|
393
421
|
enforce: 'pre',
|
|
@@ -404,40 +432,43 @@ export function one(options: One.PluginOptions = {}): PluginOption {
|
|
|
404
432
|
}
|
|
405
433
|
},
|
|
406
434
|
|
|
407
|
-
resolveId
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
435
|
+
resolveId: {
|
|
436
|
+
filter: { id: tsconfigPathsFilter },
|
|
437
|
+
handler(source: string) {
|
|
438
|
+
const jsExts = [
|
|
439
|
+
'.ts',
|
|
440
|
+
'.tsx',
|
|
441
|
+
'.js',
|
|
442
|
+
'.jsx',
|
|
443
|
+
'.mts',
|
|
444
|
+
'.mjs',
|
|
445
|
+
'.cjs',
|
|
446
|
+
'.cts',
|
|
447
|
+
]
|
|
448
|
+
for (const m of mappings) {
|
|
449
|
+
let candidate: string | undefined
|
|
450
|
+
if (m.wildcard) {
|
|
451
|
+
if (source.startsWith(m.prefix)) {
|
|
452
|
+
candidate = m.replacement + source.slice(m.prefix.length)
|
|
453
|
+
}
|
|
454
|
+
} else if (source === m.prefix) {
|
|
455
|
+
candidate = m.replacement
|
|
423
456
|
}
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
return candidate + '/index' + e
|
|
457
|
+
if (!candidate) continue
|
|
458
|
+
// already has a js/ts extension
|
|
459
|
+
if (jsExts.includes(path.extname(candidate))) return candidate
|
|
460
|
+
// try appending extensions
|
|
461
|
+
for (const e of jsExts) {
|
|
462
|
+
if (existsSync(candidate + e)) return candidate + e
|
|
463
|
+
}
|
|
464
|
+
// try /index
|
|
465
|
+
for (const e of jsExts) {
|
|
466
|
+
if (existsSync(candidate + '/index' + e))
|
|
467
|
+
return candidate + '/index' + e
|
|
468
|
+
}
|
|
469
|
+
return candidate
|
|
438
470
|
}
|
|
439
|
-
|
|
440
|
-
}
|
|
471
|
+
},
|
|
441
472
|
},
|
|
442
473
|
} satisfies Plugin
|
|
443
474
|
})(),
|
|
@@ -471,24 +502,40 @@ export function one(options: One.PluginOptions = {}): PluginOption {
|
|
|
471
502
|
android: resolveMap(a.android),
|
|
472
503
|
}
|
|
473
504
|
|
|
505
|
+
// every alias is an exact source match, so the union of all keys is
|
|
506
|
+
// an exact rust-side filter. anything else never enters js.
|
|
507
|
+
const aliasKeys = [
|
|
508
|
+
...new Set(
|
|
509
|
+
Object.values(resolved).flatMap((m) => (m ? Object.keys(m) : []))
|
|
510
|
+
),
|
|
511
|
+
]
|
|
512
|
+
const aliasFilter = aliasKeys.length
|
|
513
|
+
? new RegExp(
|
|
514
|
+
`^(?:${aliasKeys.map((k) => k.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|')})$`
|
|
515
|
+
)
|
|
516
|
+
: /(?!)/
|
|
517
|
+
|
|
474
518
|
return {
|
|
475
519
|
name: 'one:alias',
|
|
476
520
|
enforce: 'pre',
|
|
477
|
-
resolveId
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
521
|
+
resolveId: {
|
|
522
|
+
filter: { id: aliasFilter },
|
|
523
|
+
handler(source) {
|
|
524
|
+
const env = this.environment?.name
|
|
525
|
+
|
|
526
|
+
// specific env wins over general
|
|
527
|
+
const specific = env ? resolved[env as keyof typeof resolved] : null
|
|
528
|
+
if (specific && source in specific) {
|
|
529
|
+
return { id: specific[source], external: false }
|
|
530
|
+
}
|
|
485
531
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
532
|
+
// fall back to general (web/native)
|
|
533
|
+
const isWeb = !env || env === 'client' || env === 'ssr'
|
|
534
|
+
const general = isWeb ? resolved.web : resolved.native
|
|
535
|
+
if (general && source in general) {
|
|
536
|
+
return { id: general[source], external: false }
|
|
537
|
+
}
|
|
538
|
+
},
|
|
492
539
|
},
|
|
493
540
|
} satisfies Plugin
|
|
494
541
|
})(),
|
|
@@ -501,21 +548,22 @@ export function one(options: One.PluginOptions = {}): PluginOption {
|
|
|
501
548
|
name: 'one:redirect-rn-deep-imports',
|
|
502
549
|
enforce: 'pre',
|
|
503
550
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
) {
|
|
551
|
+
// rust-side filter matching the two source patterns below, so the ~24k
|
|
552
|
+
// non-matching imports in a build never enter js at all
|
|
553
|
+
resolveId: {
|
|
554
|
+
filter: { id: RN_DEEP_IMPORT_RE },
|
|
555
|
+
handler(source) {
|
|
556
|
+
if (this.environment?.name === 'client' || this.environment?.name === 'ssr') {
|
|
510
557
|
return '\0rn-empty-module'
|
|
511
558
|
}
|
|
512
|
-
}
|
|
559
|
+
},
|
|
513
560
|
},
|
|
514
561
|
|
|
515
|
-
load
|
|
516
|
-
|
|
562
|
+
load: {
|
|
563
|
+
filter: { id: exactRegex('\0rn-empty-module') },
|
|
564
|
+
handler() {
|
|
517
565
|
return 'export default {}; export {};'
|
|
518
|
-
}
|
|
566
|
+
},
|
|
519
567
|
},
|
|
520
568
|
} satisfies Plugin,
|
|
521
569
|
|
|
@@ -734,15 +782,20 @@ export function one(options: One.PluginOptions = {}): PluginOption {
|
|
|
734
782
|
name: 'one:remove-server-from-client',
|
|
735
783
|
enforce: 'pre',
|
|
736
784
|
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
785
|
+
// both conditions are rust-side: only one-server-only modules that
|
|
786
|
+
// actually contain the import cross into js
|
|
787
|
+
transform: {
|
|
788
|
+
filter: {
|
|
789
|
+
id: /one-server-only/,
|
|
790
|
+
code: 'import { AsyncLocalStorage } from "node:async_hooks"',
|
|
791
|
+
},
|
|
792
|
+
handler(code) {
|
|
793
|
+
if (this.environment.name !== 'client') return
|
|
794
|
+
return code.replace(
|
|
795
|
+
`import { AsyncLocalStorage } from "node:async_hooks"`,
|
|
796
|
+
`class AsyncLocalStorage {}`
|
|
797
|
+
)
|
|
798
|
+
},
|
|
746
799
|
},
|
|
747
800
|
},
|
|
748
801
|
|
|
@@ -773,7 +826,9 @@ export function one(options: One.PluginOptions = {}): PluginOption {
|
|
|
773
826
|
// (source) path. the optimizer doesn't recognize the real path, so it
|
|
774
827
|
// loads from source — creating a duplicate instance.
|
|
775
828
|
// this plugin forces optimized SSR deps to resolve via node_modules.
|
|
776
|
-
|
|
829
|
+
// off by default, and every hook bails on `!dedupeSymlinks`, so skip
|
|
830
|
+
// registering it entirely rather than paying a napi round trip per import
|
|
831
|
+
...(dedupeSymlinks ? [ssrSymlinkDedupPlugin] : []),
|
|
777
832
|
] satisfies Plugin[]
|
|
778
833
|
|
|
779
834
|
// TODO move to single config and through environments
|
|
@@ -38,11 +38,13 @@ const MIME: Record<string, string> = {
|
|
|
38
38
|
// FullBundleDev and diverges from the client's inlined data-uri → React hydration
|
|
39
39
|
// mismatch. So the inliner runs on client (rolldown plugin) and ssr (top-level
|
|
40
40
|
// transform) alike — see bundledDevPlugin().
|
|
41
|
+
const ASSET_EXT_RE = /\.(png|jpe?g|gif|webp|avif|bmp|ico)/
|
|
42
|
+
|
|
41
43
|
function inlineAssetImports(
|
|
42
44
|
code: string,
|
|
43
45
|
id: string
|
|
44
46
|
): { code: string; map: null } | null {
|
|
45
|
-
if (
|
|
47
|
+
if (!ASSET_EXT_RE.test(code)) return null
|
|
46
48
|
const importRe =
|
|
47
49
|
/import\s+(\w+)\s+from\s*['"]([^'"?]+\.(?:png|jpe?g|gif|webp|avif|bmp|ico))['"]\s*;?/g
|
|
48
50
|
const base = id.split('?')[0]
|
|
@@ -78,12 +80,20 @@ export function bundledDevPlugin(enabled: boolean): Plugin {
|
|
|
78
80
|
|
|
79
81
|
// mirror the client's asset inlining on the server env so SSR emits the same
|
|
80
82
|
// data-uris (the rolldown client plugin above can't reach the ssr pipeline).
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
83
|
+
// bundledDev is off by default, so only register the hook when it's on, and
|
|
84
|
+
// let rust pre-filter on the asset extensions inlineAssetImports looks for.
|
|
85
|
+
...(enabled
|
|
86
|
+
? {
|
|
87
|
+
transform: {
|
|
88
|
+
filter: { code: ASSET_EXT_RE },
|
|
89
|
+
handler(code: string, id: string) {
|
|
90
|
+
const env = (this as any).environment
|
|
91
|
+
if (!env || env.name !== 'ssr' || env.mode !== 'dev') return
|
|
92
|
+
return inlineAssetImports(code, id)
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
}
|
|
96
|
+
: {}),
|
|
87
97
|
|
|
88
98
|
config(_userConfig, env) {
|
|
89
99
|
if (!enabled || env.command !== 'serve') return
|
|
@@ -13,32 +13,16 @@ describe('criticalCSSPlugin', () => {
|
|
|
13
13
|
})
|
|
14
14
|
|
|
15
15
|
describe('resolveId', () => {
|
|
16
|
-
|
|
16
|
+
// non-matching ids are now rejected by the rust-side hook filter, so the
|
|
17
|
+
// handler never runs for them. assert the filter itself does that.
|
|
18
|
+
it('filters out non-inline CSS imports before the handler runs', async () => {
|
|
17
19
|
const { criticalCSSPlugin } = await import('./criticalCSSPlugin')
|
|
18
20
|
const plugin = criticalCSSPlugin()
|
|
21
|
+
const filter = (plugin.resolveId as any).filter.id as RegExp
|
|
19
22
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
{ resolve: vi.fn() },
|
|
24
|
-
'./styles.css',
|
|
25
|
-
resolve('/project/src/App.tsx')
|
|
26
|
-
)
|
|
27
|
-
expect(result).toBeNull()
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
it('should return null for non-css imports', async () => {
|
|
31
|
-
const { criticalCSSPlugin } = await import('./criticalCSSPlugin')
|
|
32
|
-
const plugin = criticalCSSPlugin()
|
|
33
|
-
|
|
34
|
-
;(plugin.configResolved as any)({ root: PROJECT_ROOT })
|
|
35
|
-
|
|
36
|
-
const result = await (plugin.resolveId as any).call(
|
|
37
|
-
{ resolve: vi.fn() },
|
|
38
|
-
'./data.json',
|
|
39
|
-
resolve('/project/src/App.tsx')
|
|
40
|
-
)
|
|
41
|
-
expect(result).toBeNull()
|
|
23
|
+
expect(filter.test('./styles.css')).toBe(false)
|
|
24
|
+
expect(filter.test('./data.json')).toBe(false)
|
|
25
|
+
expect(filter.test('./styles.inline.css')).toBe(true)
|
|
42
26
|
})
|
|
43
27
|
|
|
44
28
|
it('should resolve .inline.css and track it', async () => {
|
|
@@ -53,7 +37,7 @@ describe('criticalCSSPlugin', () => {
|
|
|
53
37
|
})
|
|
54
38
|
|
|
55
39
|
const importerPath = resolve('/project/src/App.tsx')
|
|
56
|
-
const result = await (plugin.resolveId as any).call(
|
|
40
|
+
const result = await (plugin.resolveId as any).handler.call(
|
|
57
41
|
{ resolve: mockResolve },
|
|
58
42
|
'./styles.inline.css',
|
|
59
43
|
importerPath
|
|
@@ -77,7 +61,7 @@ describe('criticalCSSPlugin', () => {
|
|
|
77
61
|
id: resolve('/project/src/layout.inline.css'),
|
|
78
62
|
})
|
|
79
63
|
|
|
80
|
-
await (plugin.resolveId as any).call(
|
|
64
|
+
await (plugin.resolveId as any).handler.call(
|
|
81
65
|
{ resolve: mockResolve },
|
|
82
66
|
'./layout.inline.css',
|
|
83
67
|
resolve('/project/src/App.tsx')
|
|
@@ -98,7 +82,7 @@ describe('criticalCSSPlugin', () => {
|
|
|
98
82
|
|
|
99
83
|
const mockResolve = vi.fn().mockResolvedValue(null)
|
|
100
84
|
|
|
101
|
-
const result = await (plugin.resolveId as any).call(
|
|
85
|
+
const result = await (plugin.resolveId as any).handler.call(
|
|
102
86
|
{ resolve: mockResolve },
|
|
103
87
|
'./nonexistent.inline.css',
|
|
104
88
|
resolve('/project/src/App.tsx')
|
|
@@ -119,7 +103,7 @@ describe('criticalCSSPlugin', () => {
|
|
|
119
103
|
const mockResolve = vi.fn().mockResolvedValue({
|
|
120
104
|
id: resolve('/project/app/layout.inline.css'),
|
|
121
105
|
})
|
|
122
|
-
await (plugin.resolveId as any).call(
|
|
106
|
+
await (plugin.resolveId as any).handler.call(
|
|
123
107
|
{ resolve: mockResolve },
|
|
124
108
|
'./layout.inline.css',
|
|
125
109
|
resolve('/project/app/_layout.tsx')
|
|
@@ -15,6 +15,9 @@ import type { Plugin } from 'vite'
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
const INLINE_CSS_EXT = '.inline.css'
|
|
18
|
+
// rust-side hook filter, so non-css ids never cross into js. exactly equivalent
|
|
19
|
+
// to the `id.endsWith(INLINE_CSS_EXT)` guard it replaced.
|
|
20
|
+
const INLINE_CSS_RE = /\.inline\.css$/
|
|
18
21
|
|
|
19
22
|
// source paths of CSS files with .inline.css extension (relative to root)
|
|
20
23
|
const criticalCSSSources = new Set<string>()
|
|
@@ -57,19 +60,20 @@ export function criticalCSSPlugin(): Plugin {
|
|
|
57
60
|
root = config.root
|
|
58
61
|
},
|
|
59
62
|
|
|
60
|
-
|
|
61
|
-
|
|
63
|
+
resolveId: {
|
|
64
|
+
filter: { id: INLINE_CSS_RE },
|
|
65
|
+
async handler(id, importer) {
|
|
66
|
+
const resolved = await this.resolve(id, importer, { skipSelf: true })
|
|
62
67
|
|
|
63
|
-
|
|
68
|
+
if (resolved) {
|
|
69
|
+
// store as relative path to match manifest keys (forward slashes to match Vite)
|
|
70
|
+
const relativePath = normalizePath(relative(root, resolved.id))
|
|
71
|
+
criticalCSSSources.add(relativePath)
|
|
72
|
+
return resolved
|
|
73
|
+
}
|
|
64
74
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const relativePath = normalizePath(relative(root, resolved.id))
|
|
68
|
-
criticalCSSSources.add(relativePath)
|
|
69
|
-
return resolved
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return null
|
|
75
|
+
return null
|
|
76
|
+
},
|
|
73
77
|
},
|
|
74
78
|
}
|
|
75
79
|
}
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
* platform, not by consumer type.
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
+
import { prefixRegex } from 'rolldown/filter'
|
|
22
23
|
import type { Plugin } from 'vite'
|
|
23
24
|
|
|
24
25
|
const VIRTUAL_PREFIX = '\0one-env-guard:'
|
|
@@ -30,6 +31,9 @@ const GUARD_SPECIFIERS = [
|
|
|
30
31
|
'web-only',
|
|
31
32
|
] as const
|
|
32
33
|
|
|
34
|
+
// derived so the hook filter can never drift from the list above
|
|
35
|
+
const GUARD_SPECIFIERS_RE = new RegExp(`^(?:${GUARD_SPECIFIERS.join('|')})$`)
|
|
36
|
+
|
|
33
37
|
type GuardSpecifier = (typeof GUARD_SPECIFIERS)[number]
|
|
34
38
|
type ViteEnvironment = 'client' | 'ssr' | 'ios' | 'android'
|
|
35
39
|
|
|
@@ -141,15 +145,24 @@ export function environmentGuardPlugin(options?: EnvironmentGuardOptions): Plugi
|
|
|
141
145
|
name: 'one:environment-guard',
|
|
142
146
|
enforce: 'pre',
|
|
143
147
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
148
|
+
// filters are evaluated in rust, so non-guard ids never cross into js.
|
|
149
|
+
// without them every import in the graph paid a napi round trip to be told
|
|
150
|
+
// it wasn't one of these four specifiers.
|
|
151
|
+
resolveId: {
|
|
152
|
+
filter: { id: GUARD_SPECIFIERS_RE },
|
|
153
|
+
handler(source) {
|
|
154
|
+
const envName = this.environment?.name
|
|
155
|
+
if (!envName) return null
|
|
156
|
+
const consumer = this.environment?.config?.consumer
|
|
157
|
+
return resolveEnvironmentGuard(source, envName, consumer, options)
|
|
158
|
+
},
|
|
149
159
|
},
|
|
150
160
|
|
|
151
|
-
load
|
|
152
|
-
|
|
161
|
+
load: {
|
|
162
|
+
filter: { id: prefixRegex(VIRTUAL_PREFIX) },
|
|
163
|
+
handler(id) {
|
|
164
|
+
return loadEnvironmentGuard(id)
|
|
165
|
+
},
|
|
153
166
|
},
|
|
154
167
|
}
|
|
155
168
|
}
|
|
@@ -699,9 +699,13 @@ export function createFileSystemRouterPlugin(
|
|
|
699
699
|
// 404s in this mode, so point preloads (and bootstrap) at the bundled url.
|
|
700
700
|
const clientEnv = server.environments?.client as any
|
|
701
701
|
if (clientEnv?.config?.isBundled || clientEnv?.bundledDev) {
|
|
702
|
-
// bundled mode:
|
|
703
|
-
//
|
|
704
|
-
|
|
702
|
+
// bundled mode: /@vite/client doesn't exist as a standalone module
|
|
703
|
+
// (404s). vite 8.2 moved the bundled-dev client out of the entry bundle
|
|
704
|
+
// and serves it standalone at /bundledDevClient.mjs; it defines
|
|
705
|
+
// __rolldown_runtime__, which the entry calls on nearly every module, so
|
|
706
|
+
// it has to load first or nothing hydrates. bootstrapModules emits these
|
|
707
|
+
// in order, and module scripts execute in order.
|
|
708
|
+
preloads = ['/bundledDevClient.mjs', '/assets/_virtual_one-entry.js']
|
|
705
709
|
}
|
|
706
710
|
|
|
707
711
|
// change this to .server to test using the indepedently scoped env
|
|
@@ -35,29 +35,17 @@ describe('imageDataPlugin', () => {
|
|
|
35
35
|
})
|
|
36
36
|
|
|
37
37
|
describe('resolveId', () => {
|
|
38
|
-
|
|
38
|
+
// these ids are now rejected by the rust-side hook filter, so the handler
|
|
39
|
+
// never runs for them. assert the filter itself does that.
|
|
40
|
+
it('filters out ids without a trailing ?imagedata', async () => {
|
|
39
41
|
const { imageDataPlugin } = await import('./imageDataPlugin')
|
|
40
42
|
const plugin = imageDataPlugin()
|
|
43
|
+
const filter = (plugin.resolveId as any).filter.id as RegExp
|
|
41
44
|
|
|
42
|
-
|
|
43
|
-
;(plugin.configResolved as any)(mockConfig())
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const result = await (plugin.resolveId as any)('./image.jpg', undefined)
|
|
47
|
-
expect(result).toBeNull()
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
it('should return null for imports with imagedata not as suffix', async () => {
|
|
51
|
-
const { imageDataPlugin } = await import('./imageDataPlugin')
|
|
52
|
-
const plugin = imageDataPlugin()
|
|
53
|
-
|
|
54
|
-
if (plugin.configResolved) {
|
|
55
|
-
;(plugin.configResolved as any)(mockConfig())
|
|
56
|
-
}
|
|
57
|
-
|
|
45
|
+
expect(filter.test('./image.jpg')).toBe(false)
|
|
58
46
|
// ?imagedata in the middle should not match
|
|
59
|
-
|
|
60
|
-
expect(
|
|
47
|
+
expect(filter.test('./image?imagedata.jpg')).toBe(false)
|
|
48
|
+
expect(filter.test('./image.jpg?imagedata')).toBe(true)
|
|
61
49
|
})
|
|
62
50
|
|
|
63
51
|
it('should resolve public dir paths starting with /', async () => {
|
|
@@ -68,7 +56,7 @@ describe('imageDataPlugin', () => {
|
|
|
68
56
|
;(plugin.configResolved as any)(mockConfig())
|
|
69
57
|
}
|
|
70
58
|
|
|
71
|
-
const result = await (plugin.resolveId as any)(
|
|
59
|
+
const result = await (plugin.resolveId as any).handler(
|
|
72
60
|
'/test-image.jpg?imagedata',
|
|
73
61
|
undefined
|
|
74
62
|
)
|
|
@@ -83,7 +71,7 @@ describe('imageDataPlugin', () => {
|
|
|
83
71
|
;(plugin.configResolved as any)(mockConfig())
|
|
84
72
|
}
|
|
85
73
|
|
|
86
|
-
const result = await (plugin.resolveId as any)(
|
|
74
|
+
const result = await (plugin.resolveId as any).handler(
|
|
87
75
|
'./test-image.jpg?imagedata',
|
|
88
76
|
HERO_FILE
|
|
89
77
|
)
|
|
@@ -98,7 +86,7 @@ describe('imageDataPlugin', () => {
|
|
|
98
86
|
;(plugin.configResolved as any)(mockConfig())
|
|
99
87
|
}
|
|
100
88
|
|
|
101
|
-
const result = await (plugin.resolveId as any)(
|
|
89
|
+
const result = await (plugin.resolveId as any).handler(
|
|
102
90
|
'/nonexistent.jpg?imagedata',
|
|
103
91
|
undefined
|
|
104
92
|
)
|
|
@@ -115,7 +103,7 @@ describe('imageDataPlugin', () => {
|
|
|
115
103
|
;(plugin.configResolved as any)(mockConfig())
|
|
116
104
|
}
|
|
117
105
|
|
|
118
|
-
const result = await (plugin.resolveId as any)(
|
|
106
|
+
const result = await (plugin.resolveId as any).handler(
|
|
119
107
|
'/../../../etc/passwd?imagedata',
|
|
120
108
|
undefined
|
|
121
109
|
)
|
|
@@ -130,7 +118,7 @@ describe('imageDataPlugin', () => {
|
|
|
130
118
|
;(plugin.configResolved as any)(mockConfig())
|
|
131
119
|
}
|
|
132
120
|
|
|
133
|
-
const result = await (plugin.resolveId as any)(
|
|
121
|
+
const result = await (plugin.resolveId as any).handler(
|
|
134
122
|
'../../../../etc/passwd?imagedata',
|
|
135
123
|
HERO_FILE
|
|
136
124
|
)
|
|
@@ -145,7 +133,7 @@ describe('imageDataPlugin', () => {
|
|
|
145
133
|
;(plugin.configResolved as any)(mockConfig())
|
|
146
134
|
}
|
|
147
135
|
|
|
148
|
-
const result = await (plugin.resolveId as any)(
|
|
136
|
+
const result = await (plugin.resolveId as any).handler(
|
|
149
137
|
'../../../etc/passwd?imagedata',
|
|
150
138
|
undefined
|
|
151
139
|
)
|
|
@@ -162,7 +150,7 @@ describe('imageDataPlugin', () => {
|
|
|
162
150
|
|
|
163
151
|
// Going up and back down should still work if within bounds
|
|
164
152
|
// From /project/src/components, ../.. goes to /project, then src/test-image.jpg
|
|
165
|
-
const result = await (plugin.resolveId as any)(
|
|
153
|
+
const result = await (plugin.resolveId as any).handler(
|
|
166
154
|
'../../src/test-image.jpg?imagedata',
|
|
167
155
|
HERO_FILE
|
|
168
156
|
)
|
|
@@ -171,20 +159,13 @@ describe('imageDataPlugin', () => {
|
|
|
171
159
|
})
|
|
172
160
|
|
|
173
161
|
describe('load', () => {
|
|
174
|
-
it('
|
|
162
|
+
it('filters out non-virtual modules before the handler runs', async () => {
|
|
175
163
|
const { imageDataPlugin } = await import('./imageDataPlugin')
|
|
176
164
|
const plugin = imageDataPlugin()
|
|
165
|
+
const filter = (plugin.load as any).filter.id as RegExp
|
|
177
166
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
const mockContext = {
|
|
183
|
-
addWatchFile: vi.fn(),
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
const result = await (plugin.load as any).call(mockContext, './image.jpg')
|
|
187
|
-
expect(result).toBeNull()
|
|
167
|
+
expect(filter.test('./image.jpg')).toBe(false)
|
|
168
|
+
expect(filter.test('\0imagedata:/abs/path/image.jpg')).toBe(true)
|
|
188
169
|
})
|
|
189
170
|
|
|
190
171
|
it('should return fallback data when sharp fails', async () => {
|
|
@@ -203,7 +184,7 @@ describe('imageDataPlugin', () => {
|
|
|
203
184
|
|
|
204
185
|
// This will fail because the file doesn't actually exist
|
|
205
186
|
// But it should gracefully fallback
|
|
206
|
-
const result = await (plugin.load as any).call(
|
|
187
|
+
const result = await (plugin.load as any).handler.call(
|
|
207
188
|
mockContext,
|
|
208
189
|
'\0imagedata:' + testFilePath
|
|
209
190
|
)
|
|
@@ -248,7 +229,7 @@ describe('imageDataPlugin output format', () => {
|
|
|
248
229
|
|
|
249
230
|
const testFilePath = resolve(PUBLIC_DIR, 'test-image.jpg')
|
|
250
231
|
|
|
251
|
-
const result = await (plugin.load as any).call(
|
|
232
|
+
const result = await (plugin.load as any).handler.call(
|
|
252
233
|
mockContext,
|
|
253
234
|
'\0imagedata:' + testFilePath
|
|
254
235
|
)
|