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

Commit 25b8eeb

Browse files
author
Je
committed
feat: add postcss for css/less with two plugins postcss-flexbugs-fixes and autoprefixer as default
1 parent 9030e44 commit 25b8eeb

File tree

3 files changed

+91
-31
lines changed

3 files changed

+91
-31
lines changed

project.ts

Lines changed: 75 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import marked from 'https://esm.sh/[email protected]'
2+
import postcss, { AcceptedPlugin } from 'https://esm.sh/[email protected]'
23
import { minify } from 'https://esm.sh/[email protected]'
34
import { safeLoadFront } from 'https://esm.sh/[email protected]'
45
import { Request } from './api.ts'
@@ -16,9 +17,9 @@ import { version } from './version.ts'
1617
interface Module {
1718
id: string
1819
url: string
20+
loader: string
1921
isRemote: boolean
2022
sourceFilePath: string
21-
sourceType: string
2223
sourceHash: string
2324
deps: { url: string, hash: string, async?: boolean }[]
2425
jsFile: string
@@ -53,6 +54,7 @@ export class Project {
5354
#fsWatchListeners: Array<EventEmitter> = []
5455
#renderer: Renderer = { renderPage: () => void 0, renderHead: () => void 0 }
5556
#rendered: Map<string, Map<string, RenderResult>> = new Map()
57+
#postcssPlugins: Record<string, AcceptedPlugin> = {}
5658

5759
constructor(dir: string, mode: 'development' | 'production', reload = false) {
5860
this.mode = mode
@@ -62,15 +64,22 @@ export class Project {
6264
outputDir: '/dist',
6365
baseUrl: '/',
6466
defaultLocale: 'en',
67+
env: {},
6568
locales: [],
6669
ssr: {
6770
fallback: '_fallback.html'
6871
},
6972
buildTarget: mode === 'development' ? 'es2018' : 'es2015',
7073
sourceMap: false,
71-
env: {},
7274
reactUrl: 'https://esm.sh/[email protected]',
73-
reactDomUrl: 'https://esm.sh/[email protected]'
75+
reactDomUrl: 'https://esm.sh/[email protected]',
76+
plugins: [],
77+
postcss: {
78+
plugins: [
79+
'postcss-flexbugs-fixes',
80+
'autoprefixer'
81+
]
82+
}
7483
}
7584
this.importMap = { imports: {} }
7685
this.ready = (async () => {
@@ -372,14 +381,14 @@ export class Project {
372381
// write modules
373382
const { sourceMap } = this.config
374383
await Promise.all(Array.from(outputModules).map((moduleID) => {
375-
const { sourceFilePath, sourceType, isRemote, jsContent, jsSourceMap, hash } = this.#modules.get(moduleID)!
384+
const { sourceFilePath, loader, isRemote, jsContent, jsSourceMap, hash } = this.#modules.get(moduleID)!
376385
const saveDir = path.join(distDir, path.dirname(sourceFilePath))
377386
const name = path.basename(sourceFilePath).replace(reModuleExt, '')
378387
const jsFile = path.join(saveDir, name + (isRemote ? '' : '.' + hash.slice(0, hashShort))) + '.js'
379388
if (isRemote) {
380389
logModule('deps', jsContent.length)
381390
} else {
382-
if (sourceType === 'css' || sourceType === 'less') {
391+
if (loader === 'css') {
383392
logModule('styles', jsContent.length)
384393
} else {
385394
logModule('modules', jsContent.length)
@@ -415,7 +424,7 @@ export class Project {
415424

416425
const config: Record<string, any> = {}
417426
for (const name of Array.from(['aleph.config', 'config']).map(name => ['ts', 'js', 'mjs', 'json'].map(ext => `${name}.${ext}`)).flat()) {
418-
const p = path.join(this.srcDir, name)
427+
const p = path.join(this.appRoot, name)
419428
if (existsFileSync(p)) {
420429
if (name.endsWith('.json')) {
421430
const conf = JSON.parse(await Deno.readTextFile(p))
@@ -446,7 +455,9 @@ export class Project {
446455
defaultLocale,
447456
locales,
448457
ssr,
449-
env
458+
env,
459+
plugins,
460+
postcss,
450461
} = config
451462
if (util.isNEString(srcDir)) {
452463
Object.assign(this.config, { srcDir: util.cleanPath(srcDir) })
@@ -482,10 +493,37 @@ export class Project {
482493
if (util.isPlainObject(env)) {
483494
Object.assign(this.config, { env })
484495
}
485-
// Update buildID
496+
if (util.isNEArray(plugins)) {
497+
Object.assign(this.config, { plugins })
498+
}
499+
if (util.isPlainObject(postcss) && util.isArray(postcss.plugins)) {
500+
Object.assign(this.config, { postcss })
501+
} else if (existsFileSync(path.join(this.appRoot, 'postcss.config.json'))) {
502+
const text = await Deno.readTextFile(path.join(this.appRoot, 'postcss.config.json'))
503+
try {
504+
const postcss = JSON.parse(text)
505+
if (util.isPlainObject(postcss) && util.isArray(postcss.plugins)) {
506+
Object.assign(this.config, { postcss })
507+
}
508+
} catch (e) {
509+
log.warn('bad postcss.config.json', e.message)
510+
}
511+
}
512+
// update buildID
486513
Object.assign(this, { buildID: this.mode + '.' + this.config.buildTarget })
487-
// Update routing options.
514+
// update routing options
488515
this.#routing = new Routing([], this.config.baseUrl, this.config.defaultLocale, this.config.locales)
516+
// import post plugins
517+
this.config.postcss.plugins.map(async p => {
518+
let name: string
519+
if (typeof p === 'string') {
520+
name = p
521+
} else {
522+
name = p.name
523+
}
524+
const { default: Plugin } = await import(`https://esm.sh/${name}[email protected]`)
525+
this.#postcssPlugins[name] = Plugin
526+
})
489527
}
490528

491529
private async _init(reload: boolean) {
@@ -721,12 +759,22 @@ export class Project {
721759
const isRemote = reHttp.test(url) || (url in this.importMap.imports && reHttp.test(this.importMap.imports[url]))
722760
const sourceFilePath = renameImportUrl(url)
723761
const id = (isRemote ? '//' + util.trimPrefix(sourceFilePath, '/-/') : sourceFilePath).replace(reModuleExt, '.js')
762+
let loader = ''
763+
if (reStyleModuleExt.test(url)) {
764+
loader = 'css'
765+
} else if (reMDExt.test(url)) {
766+
loader = 'markdown'
767+
} else if (reModuleExt.test(url)) {
768+
loader = 'js'
769+
} else if (isRemote) {
770+
loader = 'js'
771+
}
724772
return {
725773
id,
726774
url,
775+
loader,
727776
isRemote,
728777
sourceFilePath,
729-
sourceType: path.extname(sourceFilePath).slice(1).replace('mjs', 'js') || 'js',
730778
sourceHash: '',
731779
deps: [],
732780
jsFile: '',
@@ -863,14 +911,6 @@ export class Project {
863911
if (resp.status != 200) {
864912
throw new Error(`Download ${url}: ${resp.status} - ${resp.statusText}`)
865913
}
866-
if (mod.sourceType === 'js') {
867-
const t = resp.headers.get('Content-Type')
868-
if (t?.startsWith('text/typescript')) {
869-
mod.sourceType = 'ts'
870-
} else if (t?.startsWith('text/jsx')) {
871-
mod.sourceType = 'jsx'
872-
}
873-
}
874914
mod.sourceHash = getHash(sourceContent)
875915
sourceContent = await resp.text()
876916
shouldCompile = true
@@ -922,9 +962,9 @@ export class Project {
922962
if (shouldCompile) {
923963
const t = performance.now()
924964
mod.deps = []
925-
if (mod.sourceType === 'css' || mod.sourceType === 'less') {
965+
if (mod.loader === 'css') {
926966
let css: string = sourceContent
927-
if (mod.sourceType === 'less') {
967+
if (mod.id.endsWith('.less')) {
928968
try {
929969
// todo: sourceMap
930970
const output = await less.render(sourceContent || '/* empty content */')
@@ -933,6 +973,15 @@ export class Project {
933973
throw new Error(`less: ${error}`);
934974
}
935975
}
976+
const plugins = this.config.postcss.plugins.map(p => {
977+
if (typeof p === 'string') {
978+
return this.#postcssPlugins[p]
979+
} else {
980+
const Plugin = this.#postcssPlugins[p.name] as Function
981+
return Plugin(p.options)
982+
}
983+
})
984+
css = (await postcss(plugins).process(css).async()).content
936985
if (this.isDev) {
937986
css = String(css).trim()
938987
} else {
@@ -948,11 +997,7 @@ export class Project {
948997
].join(this.isDev ? '\n' : '')
949998
mod.jsSourceMap = ''
950999
mod.hash = getHash(css)
951-
} else if (mod.sourceType === 'sass' || mod.sourceType === 'scss') {
952-
// todo: support sass
953-
} else if (mod.sourceType === 'mdx') {
954-
// todo: support mdx
955-
} else if (mod.sourceType === 'md' || mod.sourceType === 'markdown') {
1000+
} else if (mod.loader === 'markdown') {
9561001
const { __content, ...props } = safeLoadFront(sourceContent)
9571002
const html = marked.parse(__content)
9581003
mod.jsContent = [
@@ -987,7 +1032,7 @@ export class Project {
9871032
].filter(Boolean).map(l => !this.isDev ? String(l).trim() : l).join(this.isDev ? '\n' : '')
9881033
mod.jsSourceMap = ''
9891034
mod.hash = getHash(mod.jsContent)
990-
} else {
1035+
} else if (mod.loader === 'js') {
9911036
const useDenos: string[] = []
9921037
const compileOptions = {
9931038
mode: this.mode,
@@ -1032,6 +1077,8 @@ export class Project {
10321077
useDenos.forEach(sig => {
10331078
mod.deps.push({ url: '#' + sig, hash: '', async: true })
10341079
})
1080+
} else {
1081+
throw new Error(`Unknown loader '${mod.loader}'`)
10351082
}
10361083

10371084
log.debug(`compile '${url}' in ${Math.round(performance.now() - t)}ms`)
@@ -1210,8 +1257,8 @@ export class Project {
12101257
host: 'localhost',
12111258
hostname: 'localhost',
12121259
port: '',
1213-
href: 'http://localhost' + url.pathname + url.query.toString(),
1214-
origin: 'http://localhost',
1260+
href: 'https://esm.sh' + url.pathname + url.query.toString(),
1261+
origin: 'https://esm.sh',
12151262
pathname: url.pathname,
12161263
search: url.query.toString(),
12171264
hash: '',

types.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export interface AlephEnv {
1717
* The options for **SSR**.
1818
*/
1919
export interface SSROptions {
20-
/** The options for **SSR** (default is '**_fallback.html**'). */
20+
/** The fallback html **dynamic routes** (default is '**_fallback.html**'). */
2121
fallback?: string
2222
/** A list of RegExp for paths to use **SSR**. */
2323
include?: RegExp[]
@@ -27,6 +27,15 @@ export interface SSROptions {
2727
staticPaths?: string[]
2828
}
2929

30+
/**
31+
* A plugin for **Aleph.js** application.
32+
*/
33+
export interface Plugin {
34+
test: RegExp
35+
resolve?(path: string): { path: string, external?: boolean }
36+
transform?(path: string): { code: string, sourceMap?: string, loader?: 'js' | 'json' | 'css' }
37+
}
38+
3039
/**
3140
* Config for Aleph.js application.
3241
*/
@@ -45,8 +54,12 @@ export interface Config {
4554
defaultLocale?: string
4655
/** A list of locales. */
4756
locales?: string[]
48-
/** Options for **SSR**. */
57+
/** The options for **SSR**. */
4958
ssr?: boolean | SSROptions
59+
/** A list of plugin. */
60+
plugins?: Plugin[]
61+
/** A list of plugin of PostCSS. */
62+
postcss?: { plugins: (string | { name: string, options: Record<string, any> })[] }
5063
/** `buildTarget` specifies the build target for **tsc** (possible values: '**ES2015**' - '**ES2020**' | '**ESNext**', default is **ES2015** for `production` and **ES2018** for `development`). */
5164
buildTarget?: string
5265
/** Enable sourceMap in **production** mode (default is **false**). */

util.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ const REACT_MEMO_TYPE = symbolFor ? Symbol.for('react.memo') : 0xead3
55
export const hashShort = 9
66
export const reHttp = /^https?:\/\//i
77
export const reModuleExt = /\.(js|jsx|mjs|ts|tsx)$/i
8-
export const reStyleModuleExt = /\.(css|less|sass|scss)$/i
8+
export const reStyleModuleExt = /\.(css|less)$/i
99
export const reMDExt = /\.(md|markdown)$/i
1010
export const reLocaleID = /^[a-z]{2}(-[a-zA-Z0-9]+)?$/
1111
export const reHashJs = new RegExp(`\\.[0-9a-fx]{${hashShort}}\\.js$`, 'i')

0 commit comments

Comments
 (0)