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

Commit 9e32f3e

Browse files
author
Je
committed
feat: add 2 loader plugins sass and wasm
1 parent 2675dd4 commit 9e32f3e

File tree

12 files changed

+114
-38
lines changed

12 files changed

+114
-38
lines changed

aleph.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { E400MissingDefaultExportAsComponent, E404Page, ErrorBoundary } from './
44
import events from './events.ts'
55
import { createPageProps, RouteModule, Routing } from './routing.ts'
66
import type { RouterURL } from './types.ts'
7-
import util, { hashShort, reModuleExt, reStyleModuleExt } from './util.ts'
7+
import util, { hashShort, reModuleExt } from './util.ts'
88

99
export function ALEPH({ initial }: {
1010
initial: {
@@ -47,12 +47,12 @@ export function ALEPH({ initial }: {
4747
const ctree: { id: string, Component?: ComponentType<any> }[] = pageModuleTree.map(({ id }) => ({ id }))
4848
const imports = pageModuleTree.map(async mod => {
4949
const { default: C } = await import(getModuleImportUrl(baseUrl, mod, e.forceRefetch))
50-
if (mod.asyncDeps) {
50+
if (mod.deps) {
5151
// import async dependencies
52-
for (const dep of mod.asyncDeps.filter(({ url }) => reStyleModuleExt.test(url))) {
52+
for (const dep of mod.deps.filter(({ isStyle }) => !!isStyle)) {
5353
await import(getModuleImportUrl(baseUrl, { id: dep.url.replace(reModuleExt, '.js'), hash: dep.hash }, e.forceRefetch))
5454
}
55-
if (mod.asyncDeps.filter(({ url }) => url.startsWith('#useDeno.')).length > 0) {
55+
if (mod.deps.filter(({ isData, url }) => !!isData && url.startsWith('#useDeno.')).length > 0) {
5656
const { default: data } = await import(`/_aleph/data${[url.pathname, url.query.toString()].filter(Boolean).join('@')}/data.js` + (e.forceRefetch ? `?t=${Date.now()}` : ''))
5757
if (util.isPlainObject(data)) {
5858
for (const key in data) {
@@ -143,12 +143,12 @@ export function ALEPH({ initial }: {
143143
if (url.pagePath !== '') {
144144
const imports = pageModuleTree.map(async mod => {
145145
await import(getModuleImportUrl(baseUrl, mod))
146-
if (mod.asyncDeps) {
146+
if (mod.deps) {
147147
// import async dependencies
148-
for (const dep of mod.asyncDeps.filter(({ url }) => reStyleModuleExt.test(url))) {
148+
for (const dep of mod.deps.filter(({ isStyle }) => !!isStyle)) {
149149
await import(getModuleImportUrl(baseUrl, { id: dep.url.replace(reModuleExt, '.js'), hash: dep.hash }))
150150
}
151-
if (mod.asyncDeps.filter(({ url }) => url.startsWith('#useDeno.')).length > 0) {
151+
if (mod.deps.filter(({ isData, url }) => !!isData && url.startsWith('#useDeno.')).length > 0) {
152152
const { default: data } = await import(`/_aleph/data${[url.pathname, url.query.toString()].filter(Boolean).join('@')}/data.js`)
153153
if (util.isPlainObject(data)) {
154154
for (const key in data) {

bootstrap.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React, { ComponentType } from 'https://esm.sh/react'
22
import { hydrate, render } from 'https://esm.sh/react-dom'
33
import { ALEPH, getModuleImportUrl } from './aleph.ts'
44
import { Route, RouteModule, Routing } from './routing.ts'
5-
import { reModuleExt, reStyleModuleExt } from './util.ts'
5+
import { reModuleExt } from './util.ts'
66

77
export default async function bootstrap({
88
routes,
@@ -26,9 +26,9 @@ export default async function bootstrap({
2626
const pageComponentTree: { id: string, Component?: ComponentType }[] = pageModuleTree.map(({ id }) => ({ id }))
2727
const imports = [...preloadModules, ...pageModuleTree].map(async mod => {
2828
const { default: C } = await import(getModuleImportUrl(baseUrl, mod))
29-
if (mod.asyncDeps) {
29+
if (mod.deps) {
3030
// import async dependencies
31-
for (const dep of mod.asyncDeps.filter(({ url }) => reStyleModuleExt.test(url))) {
31+
for (const dep of mod.deps.filter(({ isStyle }) => !!isStyle)) {
3232
await import(getModuleImportUrl(baseUrl, { id: dep.url.replace(reModuleExt, '.js'), hash: dep.hash }))
3333
}
3434
}

plugins/sass.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { renderSync } from 'https://esm.sh/[email protected]'
2+
3+
export default {
4+
name: 'sass-loader',
5+
test: /.(sass|scss)$/,
6+
acceptHMR: true,
7+
transform(content: Uint8Array, path: string) {
8+
const ret = renderSync({
9+
file: path,
10+
data: (new TextDecoder).decode(content),
11+
sourceMap: true
12+
})
13+
return {
14+
code: (new TextDecoder).decode(ret.css),
15+
map: ret.map ? (new TextDecoder).decode(ret.map) : undefined,
16+
loader: 'css'
17+
}
18+
}
19+
}

plugins/sass_test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { assertEquals } from 'https://deno.land/std/testing/asserts.ts';
2+
import plugin from './sass.ts';
3+
4+
Deno.test('project sass loader plugin', () => {
5+
Object.assign(window, {
6+
location: {
7+
href: 'https://localhost/'
8+
}
9+
})
10+
const { code, loader } = plugin.transform(
11+
(new TextEncoder).encode('$someVar: 123px; .some-selector { width: $someVar; }'),
12+
'test.sass'
13+
)
14+
assertEquals(plugin.test.test('test.sass'), true)
15+
assertEquals(plugin.test.test('test.scss'), true)
16+
assertEquals(plugin.acceptHMR, true)
17+
assertEquals(code, '.some-selector {\n width: 123px;\n}')
18+
assertEquals(loader, 'css')
19+
})

plugins/wasm.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
export default {
2+
name: 'wasm-loader',
3+
test: /.wasm$/,
4+
transform(content: Uint8Array, path: string) {
5+
return {
6+
code: `
7+
const wasmCode = new Uint8Array([${content.join(',')}])
8+
const wasmModule = new WebAssembly.Module(wasmCode)
9+
const { exports } = new WebAssembly.Instance(wasmModule)
10+
export default exports
11+
`,
12+
loader: 'js'
13+
}
14+
}
15+
}

plugins/wasm_test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { assertEquals } from 'https://deno.land/std/testing/asserts.ts';
2+
import plugin from './wasm.ts';
3+
4+
Deno.test('project wasm loader plugin', async () => {
5+
const wasmCode = new Uint8Array([
6+
0, 97, 115, 109, 1, 0, 0, 0, 1, 133, 128, 128, 128, 0, 1, 96, 0, 1, 127,
7+
3, 130, 128, 128, 128, 0, 1, 0, 4, 132, 128, 128, 128, 0, 1, 112, 0, 0,
8+
5, 131, 128, 128, 128, 0, 1, 0, 1, 6, 129, 128, 128, 128, 0, 0, 7, 145,
9+
128, 128, 128, 0, 2, 6, 109, 101, 109, 111, 114, 121, 2, 0, 4, 109, 97,
10+
105, 110, 0, 0, 10, 138, 128, 128, 128, 0, 1, 132, 128, 128, 128, 0, 0,
11+
65, 42, 11
12+
])
13+
const { code, loader } = plugin.transform(wasmCode, '42.wasm')
14+
const jsfile = (await Deno.makeTempFile()) + '.js'
15+
await Deno.writeTextFile(jsfile, code)
16+
const { default: wasm } = await import('file://' + jsfile)
17+
assertEquals(plugin.test.test('test.wasm'), true)
18+
assertEquals(wasm.main(), 42)
19+
assertEquals(loader, 'js')
20+
})

project.ts

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { colors, ensureDir, fromStreamReader, path, ServerRequest, Sha1, walk }
1212
import { compile } from './tsc/compile.ts'
1313
import type { AlephEnv, APIHandler, Config, RouterURL } from './types.ts'
1414
import util, { hashShort, MB, reHashJs, reHttp, reLocaleID, reMDExt, reModuleExt, reStyleModuleExt } from './util.ts'
15-
import { cleanCSS, Document, less } from './vendor/mod.ts'
15+
import { cleanCSS, less } from './vendor/mod.ts'
1616
import { version } from './version.ts'
1717

1818
interface Module {
@@ -32,7 +32,8 @@ interface Module {
3232
interface Dep {
3333
url: string
3434
hash: string
35-
async?: boolean
35+
isStyle?: boolean
36+
isData?: boolean
3637
external?: boolean
3738
}
3839

@@ -680,9 +681,6 @@ export class Project {
680681
const path = util.cleanPath(util.trimPrefix(p, this.appRoot))
681682
// handle `api` dir remove directly
682683
const validated = (() => {
683-
if (!reModuleExt.test(path) && !reStyleModuleExt.test(path) && !reMDExt.test(path)) {
684-
return false
685-
}
686684
// ignore `.aleph` and output directories
687685
if (path.startsWith('/.aleph/') || path.startsWith(this.config.outputDir)) {
688686
return false
@@ -790,8 +788,8 @@ export class Project {
790788
}
791789

792790
private _getRouteModule({ id, hash }: Module): RouteModule {
793-
const asyncDeps = this._lookupAsyncDeps(id).filter(({ async }) => !!async).map(({ async, ...rest }) => rest)
794-
return { id, hash, asyncDeps: asyncDeps.length > 0 ? asyncDeps : undefined }
791+
const deps = this._lookupDeps(id).filter(({ isData, isStyle }) => !!isData || !!isStyle).map(({ external, ...rest }) => rest)
792+
return { id, hash, deps: deps.length > 0 ? deps : undefined }
795793
}
796794

797795
private _moduleFromURL(url: string): Module {
@@ -1064,7 +1062,7 @@ export class Project {
10641062
mode: this.mode,
10651063
target: options?.forceTarget || this.config.buildTarget,
10661064
reactRefresh: this.isDev && !mod.isRemote,
1067-
rewriteImportPath: (path: string, async?: boolean) => this._resolveImportURL(mod, path, async),
1065+
rewriteImportPath: (path: string) => this._resolveImportURL(mod, path),
10681066
signUseDeno: (id: string) => {
10691067
const sig = 'useDeno.' + (new Sha1()).update(id).update(version).update(Date.now().toString()).hex().slice(0, hashShort)
10701068
useDenos.push(sig)
@@ -1101,7 +1099,7 @@ export class Project {
11011099
}
11021100
mod.hash = getHash(mod.jsContent)
11031101
useDenos.forEach(sig => {
1104-
mod.deps.push({ url: '#' + sig, hash: '', async: true })
1102+
mod.deps.push({ url: '#' + sig, hash: '', isData: true })
11051103
})
11061104
} else {
11071105
throw new Error(`Unknown loader '${mod.loader}'`)
@@ -1119,14 +1117,17 @@ export class Project {
11191117
// compile deps
11201118
for (const dep of mod.deps.filter(({ url, external }) => !url.startsWith('#useDeno.') && !external)) {
11211119
const depMod = await this._compile(dep.url)
1120+
if (depMod.loader === 'css' && !dep.isStyle) {
1121+
dep.isStyle = true
1122+
}
11221123
if (dep.hash !== depMod.hash) {
11231124
dep.hash = depMod.hash
11241125
if (!reHttp.test(dep.url)) {
11251126
const depImportPath = getRelativePath(
11261127
path.dirname(url),
11271128
dep.url.replace(reModuleExt, '')
11281129
)
1129-
mod.jsContent = mod.jsContent.replace(/(import|Import|export)([\s\S]*?)(from\s*:?\s*|\()("|')([^'"]+)("|')(\)|;)?/g, (s, key, fields, from, ql, importPath, qr, end) => {
1130+
mod.jsContent = mod.jsContent.replace(/(import|Import|export)([\s\S]*?)(from\s*:?\s*|\(|)("|')([^'"]+)("|')(\)|;)?/g, (s, key, fields, from, ql, importPath, qr, end) => {
11301131
if (
11311132
reHashJs.test(importPath) &&
11321133
importPath.slice(0, importPath.length - (hashShort + 4)) === depImportPath
@@ -1204,7 +1205,7 @@ export class Project {
12041205
})
12051206
}
12061207

1207-
private _resolveImportURL(importer: Module, url: string, async?: boolean): string {
1208+
private _resolveImportURL(importer: Module, url: string): string {
12081209
let rewrittenURL: string
12091210
let pluginsResolveRet: { url: string, external?: boolean } | null = null
12101211
for (const plugin of this.config.plugins) {
@@ -1255,7 +1256,7 @@ export class Project {
12551256
}
12561257

12571258
if (reHttp.test(url)) {
1258-
importer.deps.push({ url, hash: '', async, external: pluginsResolveRet?.external })
1259+
importer.deps.push({ url, hash: '', external: pluginsResolveRet?.external })
12591260
} else {
12601261
if (importer.isRemote) {
12611262
const sourceUrl = new URL(importer.url)
@@ -1266,14 +1267,12 @@ export class Project {
12661267
importer.deps.push({
12671268
url: sourceUrl.protocol + '//' + sourceUrl.host + pathname,
12681269
hash: '',
1269-
async,
12701270
external: pluginsResolveRet?.external
12711271
})
12721272
} else {
12731273
importer.deps.push({
12741274
url: util.cleanPath(path.dirname(importer.url) + '/' + url),
12751275
hash: '',
1276-
async,
12771276
external: pluginsResolveRet?.external
12781277
})
12791278
}
@@ -1325,8 +1324,8 @@ export class Project {
13251324
await Promise.all(imports)
13261325
const [html, data] = await this.#renderer.renderPage(url, App, undefined, pageComponentTree)
13271326
const head = await this.#renderer.renderHead([
1328-
appModule ? this._lookupAsyncDeps(appModule.id).filter(({ url }) => reStyleModuleExt.test(url)) : [],
1329-
...pageModuleTree.map(({ id }) => this._lookupAsyncDeps(id).filter(({ url }) => reStyleModuleExt.test(url)))
1327+
appModule ? this._lookupDeps(appModule.id).filter(dep => !!dep.isStyle) : [],
1328+
...pageModuleTree.map(({ id }) => this._lookupDeps(id).filter(dep => !!dep.isStyle)).flat()
13301329
].flat())
13311330
ret.head = head
13321331
ret.scripts = await Promise.all(this.#renderer.renderScripts().map(async (script: Record<string, any>) => {
@@ -1357,7 +1356,7 @@ export class Project {
13571356
const { default: E404 } = e404Module ? await import('file://' + e404Module.jsFile) : {} as any
13581357
const [html, data] = await this.#renderer.renderPage(url, undefined, E404, [])
13591358
const head = await this.#renderer.renderHead([
1360-
e404Module ? this._lookupAsyncDeps(e404Module.id).filter(({ url }) => reStyleModuleExt.test(url)) : []
1359+
e404Module ? this._lookupDeps(e404Module.id).filter(dep => !!dep.isStyle) : []
13611360
].flat())
13621361
ret.head = head
13631362
ret.scripts = await Promise.all(this.#renderer.renderScripts().map(async (script: Record<string, any>) => {
@@ -1384,7 +1383,7 @@ export class Project {
13841383
const url = { locale: this.config.defaultLocale, pagePath: '', pathname: '/', params: {}, query: new URLSearchParams() }
13851384
const [html, data] = await this.#renderer.renderPage(url, undefined, undefined, [{ id: '/loading.js', Component: Loading }])
13861385
const head = await this.#renderer.renderHead([
1387-
this._lookupAsyncDeps(loadingModule.id).filter(({ url }) => reStyleModuleExt.test(url))
1386+
this._lookupDeps(loadingModule.id).filter(dep => !!dep.isStyle)
13881387
].flat())
13891388
return {
13901389
head,
@@ -1395,7 +1394,7 @@ export class Project {
13951394
return null
13961395
}
13971396

1398-
private _lookupAsyncDeps(moduleID: string, __deps: { url: string, hash: string, async?: boolean }[] = [], __tracing: Set<string> = new Set()) {
1397+
private _lookupDeps(moduleID: string, __deps: Dep[] = [], __tracing: Set<string> = new Set()) {
13991398
const mod = this.getModule(moduleID)
14001399
if (!mod) {
14011400
return __deps
@@ -1404,10 +1403,10 @@ export class Project {
14041403
return __deps
14051404
}
14061405
__tracing.add(moduleID)
1407-
__deps.push(...mod.deps.filter(({ url, async }) => !!async && __deps.findIndex(i => i.url === url) === -1))
1406+
__deps.push(...mod.deps.filter(({ url }) => __deps.findIndex(i => i.url === url) === -1))
14081407
mod.deps.forEach(({ url }) => {
14091408
if (reModuleExt.test(url) && !reHttp.test(url)) {
1410-
this._lookupAsyncDeps(url.replace(reModuleExt, '.js'), __deps, __tracing)
1409+
this._lookupDeps(url.replace(reModuleExt, '.js'), __deps, __tracing)
14111410
}
14121411
})
14131412
return __deps
@@ -1416,7 +1415,7 @@ export class Project {
14161415

14171416
// add virtual browser global objects
14181417
Object.assign(globalThis, {
1419-
document: new Document(),
1418+
// document: new Document(),
14201419
location: {
14211420
protocol: 'http:',
14221421
host: 'localhost',

renderer.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { createPageProps } from './routing.ts'
99
import type { AlephEnv, RouterURL } from './types.ts'
1010
import util, { hashShort } from './util.ts'
1111

12-
export async function renderHead(styles?: { url: string, hash: string, async?: boolean }[]) {
12+
export async function renderHead(styles?: { url: string, hash: string }[]) {
1313
const { __buildMode, __buildTarget } = (window as any).ALEPH.ENV as AlephEnv
1414
const tags: string[] = []
1515
serverHeadElements.forEach(({ type, props }) => {
@@ -33,7 +33,7 @@ export async function renderHead(styles?: { url: string, hash: string, async?: b
3333
}
3434
}
3535
})
36-
await Promise.all(styles?.filter(({ async }) => !!async).map(({ url, hash }) => {
36+
await Promise.all(styles?.map(({ url, hash }) => {
3737
return import('file://' + util.cleanPath(`${Deno.cwd()}/.aleph/${__buildMode}.${__buildTarget}/${url}.${hash.slice(0, hashShort)}.js`))
3838
}) || [])
3939
styles?.forEach(({ url }) => {

routing.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export interface Route {
1212
export interface RouteModule {
1313
readonly id: string
1414
readonly hash: string
15-
readonly asyncDeps?: { url: string, hash: string }[]
15+
readonly deps?: { url: string, hash: string, isStyle?: boolean, isData?: boolean }[]
1616
}
1717

1818
export interface PageProps {

tsc/compile.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export interface CompileOptions {
88
mode: 'development' | 'production'
99
target: string
1010
reactRefresh: boolean
11-
rewriteImportPath: (importPath: string, async?: boolean) => string
11+
rewriteImportPath: (importPath: string) => string
1212
signUseDeno: (id: string) => string
1313
}
1414

0 commit comments

Comments
 (0)