/
css.ts
107 lines (97 loc) · 3.33 KB
/
css.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
import CleanCSS from 'https://esm.sh/clean-css@5.1.1?no-check'
import PostCSS, { AcceptedPlugin } from 'https://esm.sh/postcss@8.2.7'
import { join } from 'https://deno.land/std@0.90.0/path/mod.ts'
import { existsFileSync } from '../shared/fs.ts'
import util from '../shared/util.ts'
import type { LoaderPlugin } from '../types.ts'
const cleanCSS = new CleanCSS({ compatibility: '*' /* Internet Explorer 10+ */ })
const productionOnlyPostcssPlugins = ['autoprefixer']
type Options = {
postcss?: {
plugins: (string | [string, any] | AcceptedPlugin)[]
}
}
export default (options?: Options): LoaderPlugin => {
const decoder = new TextDecoder()
let pcssProcessor: any = null
return {
name: 'css-loader',
type: 'loader',
test: /\.p?css$/i,
acceptHMR: true,
async transform({ url, content }) {
if (pcssProcessor == null) {
pcssProcessor = await initPostCSSProcessor(options)
}
const { content: pcss } = await pcssProcessor.process(decoder.decode(content)).async()
const css = Deno.env.get('BUILD_MODE') === 'production' ? cleanCSS.minify(pcss).styles : pcss
if (url.startsWith('#inline-style-')) {
return {
code: css,
type: 'css',
map: undefined
}
}
return {
code: [
'import { applyCSS } from "https://deno.land/x/aleph/framework/core/style.ts"',
`applyCSS(${JSON.stringify(url)}, ${JSON.stringify(css)})`
].join('\n'),
map: undefined // todo: generate map
}
}
}
}
async function initPostCSSProcessor(options?: Options) {
if (options?.postcss) {
return PostCSS(await loadPostcssPlugins(options.postcss.plugins))
}
for (const name of Array.from(['ts', 'js', 'json']).map(ext => `postcss.config.${ext}`)) {
const p = join(Deno.cwd(), name)
if (existsFileSync(p)) {
let config: any = null
if (name.endsWith('.json')) {
config = JSON.parse(await Deno.readTextFile(p))
} else {
const mod = await import('file://' + p)
config = mod.default
if (util.isFunction(config)) {
config = await config()
}
}
if (isPostcssConfig(config)) {
return PostCSS(await loadPostcssPlugins(config.plugins))
}
}
}
return PostCSS(await loadPostcssPlugins(['autoprefixer']))
}
async function loadPostcssPlugins(plugins: (string | [string, any] | AcceptedPlugin)[]) {
const isDev = Deno.env.get('BUILD_MODE') === 'development'
return await Promise.all(plugins.filter(p => {
if (isDev) {
if (util.isNEString(p) && productionOnlyPostcssPlugins.includes(p)) {
return false
} else if (Array.isArray(p) && productionOnlyPostcssPlugins.includes(p[0])) {
return false
}
}
return true
}).map(async p => {
if (util.isNEString(p)) {
return await importPostcssPluginByName(p)
} else if (Array.isArray(p)) {
const Plugin = await importPostcssPluginByName(p[0])
return [Plugin, p[1]]
} else {
return p
}
}))
}
async function importPostcssPluginByName(name: string) {
const { default: Plugin } = await import(`https://esm.sh/${name}?external=postcss@8.2.4&no-check`)
return Plugin
}
function isPostcssConfig(v: any): v is { plugins: (string | [string, any] | AcceptedPlugin)[] } {
return util.isPlainObject(v) && util.isArray(v.plugins)
}