@@ -5,7 +5,6 @@ import type {
5
5
GlobalInjections ,
6
6
Optimizer ,
7
7
OptimizerOptions ,
8
- OptimizerSystem ,
9
8
QwikManifest ,
10
9
TransformModule ,
11
10
} from '../types' ;
@@ -25,7 +24,6 @@ import {
25
24
type NormalizedQwikPluginOptions ,
26
25
type QwikBuildMode ,
27
26
type QwikBuildTarget ,
28
- type QwikPackages ,
29
27
type QwikPluginOptions ,
30
28
} from './plugin' ;
31
29
import { createRollupError , normalizeRollupOutputOptions } from './rollup' ;
@@ -96,7 +94,6 @@ export function qwikVite(qwikViteOpts: QwikVitePluginOptions = {}): any {
96
94
async config ( viteConfig , viteEnv ) {
97
95
await qwikPlugin . init ( ) ;
98
96
99
- const sys = qwikPlugin . getSys ( ) ;
100
97
const path = qwikPlugin . getPath ( ) ;
101
98
102
99
let target : QwikBuildTarget ;
@@ -149,8 +146,6 @@ export function qwikVite(qwikViteOpts: QwikVitePluginOptions = {}): any {
149
146
if ( input && typeof input === 'string' ) {
150
147
input = [ input ] ;
151
148
}
152
- const shouldFindVendors =
153
- ! qwikViteOpts . disableVendorScan && ( target !== 'lib' || viteCommand === 'serve' ) ;
154
149
viteAssetsDir = viteConfig . build ?. assetsDir ;
155
150
const useAssetsDir = target === 'client' && ! ! viteAssetsDir && viteAssetsDir !== '_astro' ;
156
151
const pluginOpts : QwikPluginOptions = {
@@ -208,8 +203,6 @@ export function qwikVite(qwikViteOpts: QwikVitePluginOptions = {}): any {
208
203
clientDevInput = qwikPlugin . normalizePath ( clientDevInput ) ;
209
204
}
210
205
211
- const vendorRoots = shouldFindVendors ? await findQwikRoots ( sys , sys . cwd ( ) ) : [ ] ;
212
- const vendorIds = vendorRoots . map ( ( v ) => v . id ) ;
213
206
const isDevelopment = buildMode === 'development' ;
214
207
const qDevKey = 'globalThis.qDev' ;
215
208
const qTestKey = 'globalThis.qTest' ;
@@ -221,17 +214,11 @@ export function qwikVite(qwikViteOpts: QwikVitePluginOptions = {}): any {
221
214
222
215
const updatedViteConfig : UserConfig = {
223
216
ssr : {
224
- noExternal : [
225
- QWIK_CORE_ID ,
226
- QWIK_CORE_INTERNAL_ID ,
227
- QWIK_CORE_SERVER ,
228
- QWIK_BUILD_ID ,
229
- ...vendorIds ,
230
- ] ,
217
+ noExternal : [ QWIK_CORE_ID , QWIK_CORE_INTERNAL_ID , QWIK_CORE_SERVER , QWIK_BUILD_ID ] ,
231
218
} ,
232
219
envPrefix : [ 'VITE_' , 'PUBLIC_' ] ,
233
220
resolve : {
234
- dedupe : [ ...DEDUPE , ... vendorIds ] ,
221
+ dedupe : [ ...DEDUPE ] ,
235
222
conditions : buildMode === 'production' && target === 'client' ? [ 'min' ] : [ ] ,
236
223
alias : {
237
224
'@builder.io/qwik' : '@qwik.dev/core' ,
@@ -264,8 +251,6 @@ export function qwikVite(qwikViteOpts: QwikVitePluginOptions = {}): any {
264
251
QWIK_JSX_DEV_RUNTIME_ID ,
265
252
QWIK_BUILD_ID ,
266
253
QWIK_CLIENT_MANIFEST_ID ,
267
- // Sadly we can't specify **/*.qwik.*, so we need to specify each one
268
- ...vendorIds ,
269
254
// v1 imports, they are removed during transform but vite doesn't know that
270
255
'@builder.io/qwik' ,
271
256
'@builder.io/qwik-city' ,
@@ -653,7 +638,87 @@ export function qwikVite(qwikViteOpts: QwikVitePluginOptions = {}): any {
653
638
} ,
654
639
} as const satisfies VitePlugin < QwikVitePluginApi > ;
655
640
656
- return [ vitePluginPre , vitePluginPost ] ;
641
+ return [ vitePluginPre , vitePluginPost , checkExternals ( ) ] ;
642
+ }
643
+
644
+ /**
645
+ * This plugin checks for external dependencies that should be included in the server bundle,
646
+ * because they use Qwik. If they are not included, the optimizer won't process them, and there will
647
+ * be two instances of Qwik Core loaded.
648
+ */
649
+ async function checkExternals ( ) {
650
+ let fs : typeof import ( 'fs' ) . promises ;
651
+ let path : typeof import ( 'path' ) ;
652
+ try {
653
+ fs = await import ( 'node:fs' ) . then ( ( m ) => m . promises ) ;
654
+ path = await import ( 'node:path' ) ;
655
+ } catch {
656
+ // We can't do anything if we can't import fs and path
657
+ return ;
658
+ }
659
+ const seen : Set < string > = new Set ( ) ;
660
+ let rootDir : string ;
661
+ const core2 = '@qwik-dev/core' ;
662
+ const core1 = '@builder.io/qwik' ;
663
+ async function isQwikDep ( dep : string , dir : string ) {
664
+ while ( dir ) {
665
+ const pkg = path . join ( dir , 'node_modules' , dep , 'package.json' ) ;
666
+ try {
667
+ await fs . access ( pkg ) ;
668
+ const data = await fs . readFile ( pkg , {
669
+ encoding : 'utf-8' ,
670
+ } ) ;
671
+ // any mention of lowercase qwik in the package.json is enough
672
+ const json = JSON . parse ( data ) ;
673
+ if (
674
+ json . qwik ||
675
+ json . dependencies ?. [ core2 ] ||
676
+ json . peerDependencies ?. [ core2 ] ||
677
+ json . dependencies ?. [ core1 ] ||
678
+ json . peerDependencies ?. [ core1 ]
679
+ ) {
680
+ return true ;
681
+ }
682
+ return false ;
683
+ } catch {
684
+ //empty
685
+ }
686
+ const nextRoot = path . dirname ( dir ) ;
687
+ if ( nextRoot === dir ) {
688
+ break ;
689
+ }
690
+ dir = nextRoot ;
691
+ }
692
+ return false ;
693
+ }
694
+
695
+ return {
696
+ name : 'checkQwikExternals' ,
697
+ enforce : 'pre' ,
698
+ configResolved : ( config ) => {
699
+ rootDir = config . root ;
700
+ } ,
701
+ resolveId : {
702
+ order : 'pre' ,
703
+ async handler ( source , importer , options ) {
704
+ if ( source . startsWith ( 'node:' ) || seen . has ( source ) ) {
705
+ return ;
706
+ }
707
+ // technically we should check for each importer, but this is ok
708
+ seen . add ( source ) ;
709
+ const result = await this . resolve ( source , importer , { ...options , skipSelf : true } ) ;
710
+ if ( result ?. external ) {
711
+ // For Qwik files, check if they should be externalized
712
+ if ( await isQwikDep ( source , importer ? path . dirname ( importer ) : rootDir ) ) {
713
+ // TODO link to docs
714
+ throw new Error (
715
+ `\n==============\n${ source } is being treated as an external dependency, but it should be included in the server bundle, because it uses Qwik.\nPlease add the package to "ssr.noExternal" in the Vite config. \n==============`
716
+ ) ;
717
+ }
718
+ }
719
+ } ,
720
+ } ,
721
+ } as const satisfies VitePlugin < never > ;
657
722
}
658
723
659
724
const ANSI_COLOR = {
@@ -715,90 +780,6 @@ export async function render(document, rootNode, opts) {
715
780
}` ;
716
781
}
717
782
718
- async function findDepPkgJsonPath ( sys : OptimizerSystem , dep : string , parent : string ) {
719
- const fs : typeof import ( 'fs' ) = await sys . dynamicImport ( 'node:fs' ) ;
720
- let root = parent ;
721
- while ( root ) {
722
- const pkg = sys . path . join ( root , 'node_modules' , dep , 'package.json' ) ;
723
- try {
724
- await fs . promises . access ( pkg ) ;
725
- // use 'node:fs' version to match 'vite:resolve' and avoid realpath.native quirk
726
- // https://github.com/sveltejs/vite-plugin-svelte/issues/525#issuecomment-1355551264
727
- return fs . promises . realpath ( pkg ) ;
728
- } catch {
729
- //empty
730
- }
731
- const nextRoot = sys . path . dirname ( root ) ;
732
- if ( nextRoot === root ) {
733
- break ;
734
- }
735
- root = nextRoot ;
736
- }
737
- return undefined ;
738
- }
739
-
740
- const findQwikRoots = async (
741
- sys : OptimizerSystem ,
742
- packageJsonDir : string
743
- ) : Promise < QwikPackages [ ] > => {
744
- const paths = new Map < string , string > ( ) ;
745
- if ( sys . env === 'node' || sys . env === 'bun' ) {
746
- const fs : typeof import ( 'fs' ) = await sys . dynamicImport ( 'node:fs' ) ;
747
- let prevPackageJsonDir : string | undefined ;
748
- do {
749
- try {
750
- const data = await fs . promises . readFile ( sys . path . join ( packageJsonDir , 'package.json' ) , {
751
- encoding : 'utf-8' ,
752
- } ) ;
753
-
754
- try {
755
- const packageJson = JSON . parse ( data ) ;
756
- const dependencies = packageJson [ 'dependencies' ] ;
757
- const devDependencies = packageJson [ 'devDependencies' ] ;
758
-
759
- const packages : string [ ] = [ ] ;
760
- if ( typeof dependencies === 'object' ) {
761
- packages . push ( ...Object . keys ( dependencies ) ) ;
762
- }
763
- if ( typeof devDependencies === 'object' ) {
764
- packages . push ( ...Object . keys ( devDependencies ) ) ;
765
- }
766
-
767
- const basedir = sys . cwd ( ) ;
768
- await Promise . all (
769
- packages . map ( async ( id ) => {
770
- const pkgJsonPath = await findDepPkgJsonPath ( sys , id , basedir ) ;
771
- if ( pkgJsonPath ) {
772
- const pkgJsonContent = await fs . promises . readFile ( pkgJsonPath , 'utf-8' ) ;
773
- const pkgJson = JSON . parse ( pkgJsonContent ) ;
774
- const qwikPath = pkgJson [ 'qwik' ] ;
775
- if ( ! qwikPath ) {
776
- return ;
777
- }
778
- // Support multiple paths
779
- const allPaths = Array . isArray ( qwikPath ) ? qwikPath : [ qwikPath ] ;
780
- for ( const p of allPaths ) {
781
- paths . set (
782
- await fs . promises . realpath ( sys . path . resolve ( sys . path . dirname ( pkgJsonPath ) , p ) ) ,
783
- id
784
- ) ;
785
- }
786
- }
787
- } )
788
- ) ;
789
- } catch ( e ) {
790
- console . error ( e ) ;
791
- }
792
- } catch {
793
- // ignore errors if package.json not found
794
- }
795
- prevPackageJsonDir = packageJsonDir ;
796
- packageJsonDir = sys . path . dirname ( packageJsonDir ) ;
797
- } while ( packageJsonDir !== prevPackageJsonDir ) ;
798
- }
799
- return Array . from ( paths ) . map ( ( [ path , id ] ) => ( { path, id } ) ) ;
800
- } ;
801
-
802
783
export const isNotNullable = < T > ( v : T ) : v is NonNullable < T > => {
803
784
return v != null ;
804
785
} ;
0 commit comments