Skip to content
This repository was archived by the owner on Jul 6, 2025. It is now read-only.

Commit fb965da

Browse files
authored
Merge pull request #375 from alephjs/tailwindcss-example
Add Tailwindcss example using windi plugin
2 parents 3c5637d + 6c110a7 commit fb965da

File tree

9 files changed

+115
-29
lines changed

9 files changed

+115
-29
lines changed

bundler/mod.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ export class Bundler {
230230
entry.map((specifier) => {
231231
let mod = this.#aleph.getModule(specifier)
232232
if (mod) {
233-
hasher.update(this.#aleph.gteModuleHash(mod))
233+
hasher.update(this.#aleph.computeModuleHash(mod))
234234
}
235235
})
236236
const bundleFilename = `${name}.bundle.${hasher.toString().slice(0, 8)}.js`

examples/tailwindcss/aleph.config.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import type { Config } from 'aleph/types'
2+
import windicss from 'https://deno.land/x/[email protected]/plugin.ts'
3+
4+
export default <Config>{
5+
plugins: [windicss]
6+
}

examples/tailwindcss/app.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import React, { ComponentType } from 'react'
2+
3+
export default function App({ Page, pageProps }: { Page: ComponentType<any>, pageProps: any }) {
4+
return (
5+
<main>
6+
<head>
7+
<meta name="viewport" content="width=device-width" />
8+
</head>
9+
<Page {...pageProps} />
10+
</main>
11+
)
12+
}

examples/tailwindcss/pages/index.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import React from 'react'
2+
3+
export default function Index() {
4+
return (
5+
<div className="w-screen h-screen flex items-center justify-center">
6+
<div className="py-8 px-8 max-w-md mx-auto bg-white rounded-xl shadow-md space-y-6 sm:(py-4 space-y-0 space-x-6)">
7+
<img className="block mx-auto h-24 rounded-full sm:(mx-0 flex-shrink-0)" src="/logo.svg" alt="Aleph.js" />
8+
<div className="text-center space-y-2 sm:text-left">
9+
<div className="space-y-0.1">
10+
<p className="text-lg text-black font-semibold">Aleph.js</p>
11+
<p className="text-gray-500 font-medium">CSS Powered by Windi.</p>
12+
</div>
13+
<a
14+
href="https://alephjs.org/docs/get-started"
15+
className="inline-block px-4 py-1 text-sm text-purple-600 font-semibold rounded-full border border-purple-200 hover:(text-white bg-purple-600 border-transparent) focus:(outline-none ring-2 ring-purple-600 ring-offset-2)"
16+
>
17+
Get started
18+
</a>
19+
</div>
20+
</div>
21+
</div>
22+
)
23+
}

examples/tailwindcss/public/logo.svg

Lines changed: 15 additions & 0 deletions
Loading

framework/core/style.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,16 +45,18 @@ export function applyCSS(url: string, { css, href }: { css?: string, href?: stri
4545
return el.getAttribute('data-module-id') === url
4646
})
4747
const clean = () => {
48-
if (prevEls.length > 0) {
49-
prevEls.forEach(el => document.head.removeChild(el))
50-
}
48+
setTimeout(() => {
49+
if (prevEls.length > 0) {
50+
prevEls.forEach(el => document.head.removeChild(el))
51+
}
52+
}, 0)
5153
}
5254
let el: any
5355
if (util.isFilledString(css)) {
5456
el = document.createElement('style')
5557
el.type = 'text/css'
5658
el.appendChild(document.createTextNode(css))
57-
Promise.resolve().then(clean)
59+
clean()
5860
} else if (util.isFilledString(href)) {
5961
el = document.createElement('link')
6062
el.rel = 'stylesheet'

server/aleph.ts

Lines changed: 38 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import cssPlugin, { cssLoader } from '../plugins/css.ts'
1414
import { ensureTextFile, existsDir, existsFile, lazyRemove } from '../shared/fs.ts'
1515
import log, { Measure } from '../shared/log.ts'
1616
import util from '../shared/util.ts'
17-
import type { Aleph as IAleph, DependencyDescriptor, ImportMap, LoadInput, LoadOutput, Module, HtmlDescriptor, RouterURL, ResolveResult, TransformOutput, SSRData, RenderOutput } from '../types.d.ts'
17+
import type { Aleph as IAleph, DependencyDescriptor, ImportMap, LoadInput, LoadOutput, Module, RouterURL, ResolveResult, TransformInput, TransformOutput, SSRData, RenderOutput } from '../types.d.ts'
1818
import { VERSION } from '../version.ts'
1919
import { Analyzer } from './analyzer.ts'
2020
import { cache } from './cache.ts'
@@ -38,6 +38,7 @@ type CompileOptions = {
3838
forceRefresh?: boolean,
3939
ignoreDeps?: boolean,
4040
httpExternal?: boolean
41+
virtual?: boolean
4142
}
4243

4344
type ResolveListener = {
@@ -52,7 +53,7 @@ type LoadListener = {
5253

5354
type TransformListener = {
5455
test: RegExp | 'hmr' | 'main',
55-
transform(input: TransformOutput & { module: Module }): TransformOutput | void | Promise<TransformOutput> | Promise<void>,
56+
transform(input: TransformInput): TransformOutput | void | Promise<TransformOutput | void>,
5657
}
5758

5859
type RenderListener = (input: RenderOutput & { path: string }) => void | Promise<void>
@@ -318,7 +319,7 @@ export class Aleph implements IAleph {
318319
const module = await this.compile(specifier, {
319320
forceRefresh: true,
320321
ignoreDeps: true,
321-
httpExternal: specifier.startsWith('/api/')
322+
httpExternal: prevModule.httpExternal
322323
})
323324
const refreshPage = (
324325
this.#config.ssr &&
@@ -526,7 +527,7 @@ export class Aleph implements IAleph {
526527
}
527528

528529
/** add a module by given path and optional source code. */
529-
async addModule(specifier: string, sourceCode: string): Promise<Module> {
530+
async addModule(specifier: string, sourceCode: string, forceRefresh?: boolean): Promise<Module> {
530531
let sourceType = getSourceType(specifier)
531532
if (sourceType === SourceType.Unknown) {
532533
throw new Error("addModule: unknown souce type")
@@ -535,7 +536,8 @@ export class Aleph implements IAleph {
535536
source: {
536537
code: sourceCode,
537538
type: sourceType,
538-
}
539+
},
540+
forceRefresh,
539541
})
540542
if (specifier.startsWith('pages/') || specifier.startsWith('api/')) {
541543
specifier = '/' + specifier
@@ -707,8 +709,7 @@ export class Aleph implements IAleph {
707709
specifier: '/main.js',
708710
deps: [],
709711
sourceHash: '',
710-
jsFile: '',
711-
ready: Promise.resolve()
712+
jsFile: ''
712713
},
713714
code,
714715
})
@@ -775,7 +776,7 @@ export class Aleph implements IAleph {
775776
]
776777
}
777778

778-
gteModuleHash(module: Module) {
779+
computeModuleHash(module: Module) {
779780
const hasher = createHash('md5').update(module.sourceHash)
780781
this.lookupDeps(module.specifier, dep => {
781782
const depMod = this.getModule(dep.specifier)
@@ -939,7 +940,7 @@ export class Aleph implements IAleph {
939940

940941
async importModule<T = any>(module: Module): Promise<T> {
941942
const path = join(this.#buildDir, module.jsFile)
942-
const hash = this.gteModuleHash(module)
943+
const hash = this.computeModuleHash(module)
943944
if (existsFile(path)) {
944945
return await import(`file://${path}#${(hash).slice(0, 6)}`)
945946
}
@@ -979,7 +980,8 @@ export class Aleph implements IAleph {
979980
}
980981
for (const { test, transform } of this.#transformListeners) {
981982
if (test === 'hmr') {
982-
const ret = await transform({ module: { ...module }, code })
983+
const { jsBuffer, ready, ...rest } = module
984+
const ret = await transform({ module: structuredClone(rest), code })
983985
if (util.isFilledString(ret?.code)) {
984986
code = ret!.code
985987
}
@@ -1077,7 +1079,7 @@ export class Aleph implements IAleph {
10771079
/** init the module by given specifier, don't transpile the code when the returned `source` is equal to null */
10781080
private async initModule(
10791081
specifier: string,
1080-
{ source: customSource, forceRefresh, httpExternal }: CompileOptions = {}
1082+
{ source: customSource, forceRefresh, httpExternal, virtual }: CompileOptions = {}
10811083
): Promise<[Module, ModuleSource | null]> {
10821084
let external = false
10831085
let data: any = null
@@ -1148,7 +1150,7 @@ export class Aleph implements IAleph {
11481150
this.#appModule = mod
11491151
}
11501152

1151-
if (await existsFile(metaFp)) {
1153+
if (!forceRefresh && await existsFile(metaFp)) {
11521154
try {
11531155
const { specifier: _specifier, sourceHash, deps, isStyle, ssrPropsFn, ssgPathsFn, denoHooks } = JSON.parse(await Deno.readTextFile(metaFp))
11541156
if (_specifier === specifier && util.isFilledString(sourceHash) && util.isArray(deps)) {
@@ -1165,6 +1167,11 @@ export class Aleph implements IAleph {
11651167
} catch (e) { }
11661168
}
11671169

1170+
if (virtual) {
1171+
defer()
1172+
return [mod, null]
1173+
}
1174+
11681175
if (!isRemote || this.#reloading || mod.sourceHash === '' || !await existsFile(cacheFp)) {
11691176
try {
11701177
const src = customSource || await this.resolveModuleSource(specifier, data)
@@ -1272,15 +1279,20 @@ export class Aleph implements IAleph {
12721279
}
12731280
}
12741281

1282+
let extraDeps: DependencyDescriptor[] = []
12751283
for (const { test, transform } of this.#transformListeners) {
12761284
if (test instanceof RegExp && test.test(specifier)) {
1277-
const ret = await transform({ module: { ...module }, code: jsCode, map: sourceMap })
1285+
const { jsBuffer, ready, ...rest } = module
1286+
const ret = await transform({ module: { ...structuredClone(rest) }, code: jsCode, map: sourceMap })
12781287
if (util.isFilledString(ret?.code)) {
12791288
jsCode = ret!.code
12801289
}
12811290
if (util.isFilledString(ret?.map)) {
12821291
sourceMap = ret!.map
12831292
}
1293+
if (Array.isArray(ret?.extraDeps)) {
1294+
extraDeps.push(...ret!.extraDeps)
1295+
}
12841296
}
12851297
}
12861298

@@ -1291,7 +1303,7 @@ export class Aleph implements IAleph {
12911303

12921304
module.jsBuffer = encoder.encode(jsCode)
12931305
module.deps = deps.filter(({ specifier }) => specifier !== module.specifier).map(({ specifier, resolved, isDynamic }) => {
1294-
const dep: DependencyDescriptor = { specifier }
1306+
const dep: DependencyDescriptor = { specifier, }
12951307
if (isDynamic) {
12961308
dep.isDynamic = true
12971309
}
@@ -1303,7 +1315,7 @@ export class Aleph implements IAleph {
13031315
}
13041316
}
13051317
return dep
1306-
})
1318+
}).concat(extraDeps)
13071319

13081320
ms.stop(`transpile '${specifier}'`)
13091321

@@ -1312,11 +1324,16 @@ export class Aleph implements IAleph {
13121324

13131325
if (module.deps.length > 0) {
13141326
let fsync = false
1315-
await Promise.all(module.deps.map(async ({ specifier, hashLoc }) => {
1316-
let depModule: Module | null
1317-
if (ignoreDeps) {
1327+
await Promise.all(module.deps.map(async ({ specifier, hashLoc, virtual }) => {
1328+
let depModule: Module | null = null
1329+
if (ignoreDeps || virtual) {
13181330
depModule = this.getModule(specifier)
1319-
} else {
1331+
if (depModule == null && virtual) {
1332+
const [mod] = await this.initModule(specifier, { virtual: true })
1333+
depModule = mod
1334+
}
1335+
}
1336+
if (depModule === null) {
13201337
const [mod, src] = await this.initModule(specifier, { httpExternal })
13211338
if (!mod.external) {
13221339
await this.transpileModule(mod, src, false, __tracing)
@@ -1325,7 +1342,7 @@ export class Aleph implements IAleph {
13251342
}
13261343
if (depModule) {
13271344
if (hashLoc !== undefined) {
1328-
const hash = this.gteModuleHash(depModule)
1345+
const hash = this.computeModuleHash(depModule)
13291346
if (await this.replaceDepHash(module, hashLoc, hash)) {
13301347
fsync = true
13311348
}
@@ -1356,7 +1373,7 @@ export class Aleph implements IAleph {
13561373
const { specifier, hashLoc } = dep
13571374
if (specifier === by.specifier && hashLoc !== undefined) {
13581375
if (hash == null) {
1359-
hash = this.gteModuleHash(by)
1376+
hash = this.computeModuleHash(by)
13601377
}
13611378
if (await this.replaceDepHash(mod, hashLoc, hash)) {
13621379
fsync = true

server/server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ export class Server {
175175
if (module) {
176176
const content = await aleph.getModuleJS(module, aleph.isDev)
177177
if (content) {
178-
const hash = aleph.gteModuleHash(module)
178+
const hash = aleph.computeModuleHash(module)
179179
if (hash === req.headers.get('If-None-Match')) {
180180
end(304)
181181
return

types.d.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ export interface Aleph {
66
readonly workingDir: string
77
readonly config: RequiredConfig
88
addDist(path: string, content: Uint8Array): Promise<void>
9-
addModule(specifier: string, sourceCode: string): Promise<{ specifier: string, jsFile: string }>
9+
addModule(specifier: string, sourceCode: string, forceRefresh?: boolean): Promise<Module>
1010
fetchModule(specifier: string): Promise<{ content: Uint8Array, contentType: string | null }>
1111
onResolve(test: RegExp, resolve: (specifier: string) => ResolveResult): void
1212
onLoad(test: RegExp, load: (input: LoadInput) => LoadOutput | Promise<LoadOutput>): void
13-
onTransform(test: 'hmr' | 'main' | RegExp, transform: (input: TransformOutput & { module: Module }) => TransformOutput | void | Promise<TransformOutput> | Promise<void>): void
13+
onTransform(test: 'hmr' | 'main' | RegExp, transform: (input: TransformInput) => TransformOutput | void | Promise<TransformOutput | void>): void
1414
onRender(callback: (input: RenderOutput) => Promise<void> | void): void
1515
}
1616

@@ -85,11 +85,21 @@ export type LoadOutput = {
8585
map?: string
8686
}
8787

88+
/**
89+
* The input of the `onTransform` hook.
90+
*/
91+
export type TransformInput = {
92+
module: Omit<Module, 'jsBuffer' | 'ready'>
93+
code: string
94+
map?: string
95+
}
96+
8897
/**
8998
* The output of the `onTransform` hook.
9099
*/
91100
export type TransformOutput = {
92101
code: string
102+
extraDeps?: DependencyDescriptor[]
93103
map?: string
94104
}
95105

@@ -113,6 +123,7 @@ export type Module = {
113123
/** The Dependency Descriptor. */
114124
type DependencyDescriptor = {
115125
readonly specifier: string
126+
virtual?: boolean
116127
isDynamic?: boolean
117128
hashLoc?: number
118129
}

0 commit comments

Comments
 (0)