88 * closeBundle() in less:build plugin. No longer a standalone CLI entry.
99 * ctx parameter is required (no globalThis fallback).
1010 *
11+ * Resolution: No Vite resolve.alias. The client build runs with
12+ * root = workspace root so Vite finds the workspace deno.json
13+ * and resolves all @lessjs/* packages through Deno natively.
14+ *
1115 * Usage:
1216 * deno task build (unified entry — runs all 3 phases)
1317 */
1418
1519import { build as viteBuild , type InlineConfig } from 'vite' ;
16- import { resolve } from 'node:path' ;
20+ import { basename , resolve } from 'node:path' ;
1721import process from 'node:process' ;
1822import { type ClientIslandEntry , generateClientEntry } from '../entry-generators.js' ;
1923import type { LessBuildContext } from '../build-context.js' ;
@@ -24,121 +28,17 @@ const log = createLogger('ssg');
2428const VIRTUAL_CLIENT_ENTRY_ID = 'virtual:less-client-entry' ;
2529const RESOLVED_CLIENT_ENTRY_ID = '\0' + VIRTUAL_CLIENT_ENTRY_ID ;
2630
27- // ─── Alias auto-generation from workspace ────────────────────────────
28-
29- interface AliasEntry {
30- find : string ;
31- replacement : string ;
32- }
33-
34- /**
35- * Walk up from startDir to find a deno.json with a "workspace" field.
36- * Returns the directory containing the workspace deno.json, or null.
37- */
38- async function findWorkspaceRoot ( startDir : string ) : Promise < string | null > {
39- let dir = resolve ( startDir ) ;
40- const fsRoot = resolve ( '/' ) ;
41- while ( dir !== fsRoot && dir !== resolve ( dir , '..' ) ) {
42- try {
43- const path = resolve ( dir , 'deno.json' ) ;
44- const raw = await Deno . readTextFile ( path ) ;
45- const cfg = JSON . parse ( raw ) ;
46- if ( cfg . workspace && Array . isArray ( cfg . workspace ) ) return dir ;
47- } catch { /* not found or no workspace */ }
48- dir = resolve ( dir , '..' ) ;
49- }
50- return null ;
51- }
52-
53- /**
54- * Generate Vite resolve.alias entries from all @lessjs/* workspace packages.
55- * Each package's deno.json exports become aliases:
56- * "." → { find: "@lessjs /pkg", replacement: "<dir>/src/index.ts" }
57- * "./foo" → { find: "@lessjs/pkg/foo", replacement: "<dir>/src/foo.ts" }
58- */
59- async function generateWorkspaceAliases ( workspaceRoot : string ) : Promise < AliasEntry [ ] > {
60- const rootConfig = JSON . parse (
61- await Deno . readTextFile ( resolve ( workspaceRoot , 'deno.json' ) ) ,
62- ) ;
63- const members : string [ ] = rootConfig . workspace || [ ] ;
64- const aliases : AliasEntry [ ] = [ ] ;
65-
66- for ( const member of members ) {
67- const memberDir = resolve ( workspaceRoot , member ) ;
68- let memberCfg : Record < string , unknown > ;
69- try {
70- memberCfg = JSON . parse ( await Deno . readTextFile ( resolve ( memberDir , 'deno.json' ) ) ) ;
71- } catch {
72- continue ;
73- }
74-
75- const name = memberCfg . name as string | undefined ;
76- const exports = memberCfg . exports as Record < string , string > | string | undefined ;
77- if ( ! name || ! exports ) continue ;
78-
79- if ( typeof exports === 'string' ) {
80- // Single export — no subpaths
81- aliases . push ( { find : name , replacement : resolve ( memberDir , exports ) } ) ;
82- continue ;
83- }
84-
85- // Build subpath aliases first (Vite prefix matching: subpath before parent)
86- for ( const [ exportPath , sourcePath ] of Object . entries ( exports ) ) {
87- if ( exportPath === '.' ) continue ; // parent alias added last
88- // "./foo" → "@lessjs/pkg/foo"
89- const subpath = exportPath . replace ( / ^ \. \/ / , '/' ) ;
90- aliases . push ( {
91- find : `${ name } ${ subpath } ` ,
92- replacement : resolve ( memberDir , sourcePath as string ) ,
93- } ) ;
94- }
95-
96- // Parent alias last
97- if ( exports [ '.' ] ) {
98- aliases . push ( { find : name , replacement : resolve ( memberDir , exports [ '.' ] as string ) } ) ;
99- }
100- }
101-
102- return aliases ;
103- }
104-
105- // ─── Build function ──────────────────────────────────────────────────
106-
10731async function buildClient ( ctx : LessBuildContext ) : Promise < void > {
108- const root = ctx . root || process . cwd ( ) ;
32+ const userRoot = ctx . root || process . cwd ( ) ;
33+ const workspaceRoot = resolve ( userRoot , '..' ) ;
34+ const wwwRel = basename ( userRoot ) ; // e.g. "www"
35+
10936 const outDir = ctx . outDir || 'dist' ;
11037 const islandsDir = ctx . islandsDir || 'app/islands' ;
11138 const localIslands = ctx . islandTagNames || [ ] ;
11239 const localIslandFiles = ctx . islandFiles || [ ] ;
11340 const packageIslands = ctx . packageIslands || [ ] ;
11441
115- // Resolve alias for client build
116- // 1. Prefer user-provided aliases (vite.config.ts resolve.alias)
117- // 2. Otherwise, auto-generate from Deno workspace packages
118- const resolveAlias = ctx . userResolveAlias ;
119- let serializedAlias : AliasEntry [ ] | null = null ;
120-
121- if ( resolveAlias ) {
122- serializedAlias = Array . isArray ( resolveAlias )
123- ? resolveAlias . filter ( ( a ) => typeof a . find === 'string' ) . map ( ( a ) => ( {
124- find : a . find as string ,
125- replacement : a . replacement ,
126- } ) )
127- : Object . entries ( resolveAlias ) . map ( ( [ find , replacement ] ) => ( { find, replacement } ) ) ;
128- log . info ( 'resolveAlias: ' + JSON . stringify ( serializedAlias , null , 2 ) ) ;
129- } else {
130- // Auto-generate from workspace packages
131- const workspaceRoot = await findWorkspaceRoot ( root ) ;
132- if ( workspaceRoot ) {
133- serializedAlias = await generateWorkspaceAliases ( workspaceRoot ) ;
134- log . info (
135- `Auto-generated ${ serializedAlias . length } alias(es) from workspace: ${ workspaceRoot } ` ,
136- ) ;
137- } else {
138- log . info ( 'WARNING: no resolveAlias and no workspace found — island imports may fail' ) ;
139- }
140- }
141-
14242 if ( localIslands . length === 0 && packageIslands . length === 0 ) {
14343 log . info ( 'No islands found — zero client JS output' ) ;
14444 return ;
@@ -152,7 +52,7 @@ async function buildClient(ctx: LessBuildContext): Promise<void> {
15252 ...localIslands . map ( ( tagName : string , i : number ) => ( {
15353 tagName,
15454 modulePath : resolve (
155- root ,
55+ userRoot ,
15656 localIslandFiles [ i ]
15757 ? `${ islandsDir } /${ localIslandFiles [ i ] } `
15858 : `${ islandsDir } /${ tagName } .ts` ,
@@ -187,11 +87,13 @@ async function buildClient(ctx: LessBuildContext): Promise<void> {
18787 return item ;
18888 } ) ;
18989
190- const clientOutDir = resolve ( root , outDir , 'client' ) ;
90+ const clientOutDir = resolve ( userRoot , outDir , 'client' ) ;
19191 const clientBase = ctx . base || '/' ;
19292 const clientConfig : InlineConfig = {
19393 configFile : false ,
194- root,
94+ // Use workspace root so Vite finds workspace deno.json
95+ // and resolves @lessjs /* through Deno natively (zero aliases).
96+ root : workspaceRoot ,
19597 base : `${ clientBase } client/` ,
19698 logLevel : 'warn' ,
19799 build : {
@@ -207,7 +109,8 @@ async function buildClient(ctx: LessBuildContext): Promise<void> {
207109 entryFileNames : 'islands/[name].js' ,
208110 chunkFileNames : 'islands/[name]-[hash].js' ,
209111 manualChunks ( id : string ) {
210- if ( id . includes ( `/${ islandsDir } /` ) ) {
112+ // Match local islands under www/ (root is workspace root)
113+ if ( id . includes ( `/${ wwwRel } /${ islandsDir } /` ) ) {
211114 const match = id . match ( / \/ ( [ ^ / ] + ) \. ( t s | j s ) $ / ) ;
212115 if ( match ) return `island-${ match [ 1 ] } ` ;
213116 }
@@ -218,7 +121,6 @@ async function buildClient(ctx: LessBuildContext): Promise<void> {
218121 } ,
219122 } ,
220123 } ,
221- resolve : serializedAlias ? { alias : serializedAlias } : undefined ,
222124 ssr : {
223125 noExternal : ( noExternalPatterns . length > 0 ? noExternalPatterns : undefined ) as
224126 | ( string | RegExp ) [ ]
0 commit comments