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

Commit 3fd160e

Browse files
author
Je
committed
feat: new Import api
1 parent 9797ba1 commit 3fd160e

File tree

8 files changed

+144
-64
lines changed

8 files changed

+144
-64
lines changed

head.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import React, { Children, createElement, isValidElement, PropsWithChildren, ReactElement, ReactNode, useEffect } from 'https://esm.sh/react'
2+
import { resetImports } from './importor.ts'
23
import util from './util.ts'
34

45
const serverHeadElements: Array<{ type: string, props: Record<string, any> }> = []
56
const serverStyles: Map<string, { css: string, asLink: boolean }> = new Map()
67

7-
export function renderHead(styleModules?: string[]) {
8+
export async function renderHead(styleModules?: string[]) {
89
const tags: string[] = []
910
serverHeadElements.forEach(({ type, props }) => {
1011
if (type === 'title') {
@@ -27,6 +28,7 @@ export function renderHead(styleModules?: string[]) {
2728
}
2829
}
2930
})
31+
await Promise.all(resetImports().map(path => import(path)))
3032
styleModules?.filter(id => serverStyles.has(id)).forEach(id => {
3133
const { css, asLink } = serverStyles.get(id)!
3234
if (asLink) {

importor.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import React, { useEffect } from 'https://esm.sh/react'
2+
import type { Config } from './types.ts'
3+
import util, { reStyleModuleExt } from './util.ts'
4+
5+
const serverImports: Set<string> = new Set()
6+
7+
// reset imports and returns them
8+
export function resetImports() {
9+
const a = Array.from(serverImports)
10+
serverImports.clear()
11+
return a
12+
}
13+
14+
interface ImportProps {
15+
from: string
16+
rawPath: string
17+
resolveDir: string
18+
}
19+
20+
export function Import({ from, rawPath, resolveDir }: ImportProps) {
21+
if (reStyleModuleExt.test(rawPath)) {
22+
return React.createElement(StyleLoader, { path: from, rawPath, resolveDir })
23+
}
24+
// todo: more loaders
25+
return null
26+
}
27+
28+
interface LoaderProps {
29+
path: string
30+
rawPath: string
31+
resolveDir: string
32+
}
33+
34+
export function StyleLoader({ path, rawPath, resolveDir }: LoaderProps) {
35+
if (typeof Deno !== 'undefined') {
36+
const { appDir, mode, config } = (window as any).ALEPH_ENV as { appDir: string, mode: string, config: Config }
37+
serverImports.add(util.cleanPath(`${appDir}/.aleph/${mode}.${config.buildTarget}/${resolveDir}/${path}`))
38+
}
39+
40+
useEffect(() => {
41+
import(util.cleanPath(`/_aleph/${resolveDir}/${path}`))
42+
return () => {
43+
const moduleId = util.cleanPath(`./${resolveDir}/${rawPath}`)
44+
const { document } = (window as any)
45+
Array.from(document.head.children).forEach((el: any) => {
46+
if (el.getAttribute('data-module-id') === moduleId) {
47+
document.head.removeChild(el)
48+
}
49+
})
50+
}
51+
}, [])
52+
53+
return null
54+
}

mod.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@ export * from './context.ts'
33
export { ErrorPage } from './error.ts'
44
export { default as Head, SEO, Viewport } from './head.ts'
55
export * from './hooks.ts'
6+
export { Import } from './importor.ts'
67
export { default as Link } from './link.ts'
78

project.ts

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,11 @@ import { createRouter } from './router.ts'
66
import { colors, ensureDir, path, Sha1, walk } from './std.ts'
77
import { compile } from './tsc/compile.ts'
88
import type { APIHandle, Config, Location, RouterURL } from './types.ts'
9-
import util, { hashShort } from './util.ts'
9+
import util, { hashShort, reHashJs, reHttp, reModuleExt, reStyleModuleExt } from './util.ts'
1010
import './vendor/clean-css-builds/v4.2.2.js'
1111
import { Document } from './vendor/deno-dom/document.ts'
1212
import less from './vendor/less/less.js'
1313

14-
const reHttp = /^https?:\/\//i
15-
const reModuleExt = /\.(js|jsx|mjs|ts|tsx)$/i
16-
const reStyleModuleExt = /\.(css|less|sass|scss)$/i
17-
const reHashJs = new RegExp(`\\.[0-9a-fx]{${hashShort}}\\.js$`, 'i')
18-
1914
const { CleanCSS } = window as any
2015
const cleanCSS = new CleanCSS({ compatibility: '*' /* Internet Explorer 10+ */ })
2116

@@ -396,6 +391,8 @@ export default class Project {
396391
Object.assign(globalThis, {
397392
ALEPH_ENV: {
398393
appDir: this.rootDir,
394+
config: this.config,
395+
mode: this.mode,
399396
},
400397
document: new Document(),
401398
innerWidth: 1920,
@@ -913,12 +910,12 @@ export default class Project {
913910
path.dirname(path.resolve('/', url)),
914911
path.resolve('/', dep.url.replace(reModuleExt, ''))
915912
)
916-
mod.jsContent = mod.jsContent.replace(/(import|export)([^'"]*)("|')([^'"]+)("|')(\)|;)?/g, (s, key, from, ql, importPath, qr, end) => {
913+
mod.jsContent = mod.jsContent.replace(/(i|Import|export)([^'"]*)("|')([^'"]+)("|')(\)|;)?/g, (s, key, from, ql, importPath, qr, end) => {
917914
if (
918915
reHashJs.test(importPath) &&
919916
importPath.slice(0, importPath.length - (hashShort + 4)) === depImportPath
920917
) {
921-
return `${key}${from}${ql}${depImportPath}.${dep.hash.slice(0, hashShort)}.js${qr}${end}`
918+
return `${key}${from}${ql}${depImportPath}.${dep.hash.slice(0, hashShort)}.js${qr}${end || ''}`
922919
}
923920
return s
924921
})
@@ -965,7 +962,7 @@ export default class Project {
965962
reHashJs.test(importPath) &&
966963
importPath.slice(0, importPath.length - (hashShort + 4)) === depImportPath
967964
) {
968-
return `${key}${from}${ql}${depImportPath}.${dep.hash.slice(0, hashShort)}.js${qr}${end}`
965+
return `${key}${from}${ql}${depImportPath}.${dep.hash.slice(0, hashShort)}.js${qr}${end || ''}`
969966
}
970967
return s
971968
})
@@ -1082,7 +1079,7 @@ export default class Project {
10821079
const { default: Page } = await import("file://" + pageModule.jsFile)
10831080
const data = await this.getData()
10841081
const html = renderPage(data, url, appModule ? App : undefined, Page)
1085-
const head = renderHead([
1082+
const head = await renderHead([
10861083
this._lookupStyles(pageModule),
10871084
appModule ? this._lookupStyles(appModule) : []
10881085
].flat())

tsc/compile.ts

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import ts from 'https://esm.sh/typescript'
22
import transformImportPathRewrite from './transform-import-path-rewrite.ts'
3-
import transformReactJsxSource from './transform-react-jsx-source.ts'
3+
import transformReactJsx from './transform-react-jsx.ts'
44
import transformReactRefresh from './transform-react-refresh.ts'
55
import { CreatePlainTransformer, CreateTransformer } from './transformer.ts'
66

77
export interface CompileOptions {
8-
target?: string
9-
mode?: 'development' | 'production'
10-
rewriteImportPath?: (importPath: string) => string
11-
reactRefresh?: boolean
8+
target: string
9+
mode: 'development' | 'production'
10+
reactRefresh: boolean
11+
rewriteImportPath: (importPath: string) => string
1212
}
1313

1414
export function createSourceFile(fileName: string, source: string) {
@@ -29,18 +29,14 @@ const allowTargets = [
2929
'es2020',
3030
]
3131

32-
export function compile(fileName: string, source: string, { target: targetName = 'ES2015', mode, rewriteImportPath, reactRefresh }: CompileOptions) {
32+
export function compile(fileName: string, source: string, { target: targetName, mode, rewriteImportPath, reactRefresh }: CompileOptions) {
3333
const target = allowTargets.indexOf(targetName.toLowerCase())
3434
const transformers: ts.CustomTransformers = { before: [], after: [] }
35-
if (mode === 'development') {
36-
transformers.before!.push(CreatePlainTransformer(transformReactJsxSource))
37-
}
35+
transformers.before!.push(CreatePlainTransformer(transformReactJsx, { mode, rewriteImportPath }))
3836
if (reactRefresh) {
3937
transformers.before!.push(CreateTransformer(transformReactRefresh))
4038
}
41-
if (rewriteImportPath) {
42-
transformers.after!.push(CreatePlainTransformer(transformImportPathRewrite, rewriteImportPath))
43-
}
39+
transformers.after!.push(CreatePlainTransformer(transformImportPathRewrite, rewriteImportPath))
4440

4541
return ts.transpileModule(source, {
4642
fileName,

tsc/transform-react-jsx-source.ts

Lines changed: 0 additions & 41 deletions
This file was deleted.

tsc/transform-react-jsx.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import ts from 'https://esm.sh/typescript'
2+
import { path } from '../std.ts'
3+
4+
export default function transformReactJsx(sf: ts.SourceFile, node: ts.Node, options: { mode: 'development' | 'production', rewriteImportPath: (importPath: string) => string }): ts.VisitResult<ts.Node> {
5+
if (ts.isJsxOpeningElement(node) || ts.isJsxSelfClosingElement(node)) {
6+
let props = Array.from(node.attributes.properties)
7+
8+
if (node.tagName.getText() === 'Import') {
9+
let rawPath = ''
10+
for (let i = 0; i < props.length; i++) {
11+
const prop = props[i]
12+
if (ts.isJsxAttribute(prop) && prop.name.text === 'from' && prop.initializer && ts.isStringLiteral(prop.initializer)) {
13+
rawPath = prop.initializer.text
14+
props[i] = ts.createJsxAttribute(
15+
ts.createIdentifier('from'),
16+
ts.createJsxExpression(undefined, ts.createStringLiteral(options.rewriteImportPath(rawPath)))
17+
)
18+
break
19+
}
20+
}
21+
if (rawPath) {
22+
props.push(
23+
ts.createJsxAttribute(
24+
ts.createIdentifier('rawPath'),
25+
ts.createJsxExpression(undefined, ts.createStringLiteral(rawPath))
26+
),
27+
ts.createJsxAttribute(
28+
ts.createIdentifier('resolveDir'),
29+
ts.createJsxExpression(undefined, ts.createStringLiteral(path.dirname(sf.fileName)))
30+
)
31+
)
32+
}
33+
}
34+
35+
if (options.mode === 'development') {
36+
const fileNameAttr = ts.createPropertyAssignment(
37+
'fileName',
38+
ts.createStringLiteral(sf.fileName)
39+
)
40+
const lineNumberAttr = ts.createPropertyAssignment(
41+
'lineNumber',
42+
ts.createNumericLiteral((sf.getLineAndCharacterOfPosition(node.pos).line + 1).toString())
43+
)
44+
const prop = ts.createJsxAttribute(
45+
ts.createIdentifier('__source'),
46+
ts.createJsxExpression(undefined, ts.createObjectLiteral([fileNameAttr, lineNumberAttr]))
47+
)
48+
props.push(prop)
49+
}
50+
51+
if (ts.isJsxSelfClosingElement(node)) {
52+
return ts.createJsxSelfClosingElement(
53+
node.tagName,
54+
node.typeArguments,
55+
ts.createJsxAttributes(props)
56+
)
57+
} else if (ts.isJsxOpeningElement(node)) {
58+
return ts.createJsxOpeningElement(
59+
node.tagName,
60+
node.typeArguments,
61+
ts.createJsxAttributes(props)
62+
)
63+
}
64+
}
65+
}

util.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
11
export const hashShort = 7
2+
export const reHttp = /^https?:\/\//i
3+
export const reModuleExt = /\.(js|jsx|mjs|ts|tsx)$/i
4+
export const reStyleModuleExt = /\.(css|less|sass|scss)$/i
5+
export const reSVGExt = /\.(svg|svgz)$/i
6+
export const reMDExt = /\.(md|mdx)$/i
7+
export const reHashJs = new RegExp(`\\.[0-9a-fx]{${hashShort}}\\.js$`, 'i')
28

39
export default {
410
isNumber(a: any): a is number {

0 commit comments

Comments
 (0)