-
Notifications
You must be signed in to change notification settings - Fork 169
/
css.ts
134 lines (122 loc) · 4.01 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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
import { Plugin, PluginCreator } from 'https://esm.sh/postcss@8.2.8'
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 postcssVersion = '8.2.8'
const productionOnlyPostcssPlugins = ['autoprefixer']
const decoder = new TextDecoder()
const importDecl = 'import { applyCSS } from "https://deno.land/x/aleph/framework/core/style.ts"'
export type AcceptedPlugin = string | [string, any] | Plugin | PluginCreator<any>
export type Options = {
postcss?: {
plugins: AcceptedPlugin[]
}
}
export default (options?: Options): LoaderPlugin => {
let pcssProcessor: any = null
let cleanCSS: any = null
let isProd: any = null
return {
name: 'css-loader',
type: 'loader',
test: /\.p?css$/i,
acceptHMR: true,
async resolve(url: string) {
if (util.isLikelyHttpURL(url)) {
return Promise.resolve(new Uint8Array())
}
return Deno.readFile(join(Deno.cwd(), url))
},
async transform({ url, content }) {
if (util.isLikelyHttpURL(url)) {
return {
code: [
importDecl,
`applyCSS(${JSON.stringify(url)})`
].join('\n')
}
}
if (isProd === null) {
isProd = Deno.env.get('BUILD_MODE') === 'production'
}
if (isProd) {
const { default: CleanCSS } = await import('https://esm.sh/clean-css@5.1.2?no-check')
cleanCSS = new CleanCSS({ compatibility: '*' /* Internet Explorer 10+ */ })
}
if (pcssProcessor == null) {
pcssProcessor = await initPostCSSProcessor(options)
}
const { content: pcss } = await pcssProcessor.process(decoder.decode(content)).async()
const css = isProd ? cleanCSS.minify(pcss).styles : pcss
if (url.startsWith('#inline-style-')) {
return {
code: css,
type: 'css',
map: undefined
}
}
return {
code: [
importDecl,
`applyCSS(${JSON.stringify(url)}, ${JSON.stringify(css)})`
].join('\n')
// todo: generate map
}
}
}
}
async function initPostCSSProcessor(options?: Options) {
const { default: PostCSS } = await import(`https://esm.sh/postcss@${postcssVersion}`)
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: 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@${postcssVersion}&no-check`)
return Plugin
}
function isPostcssConfig(v: any): v is { plugins: AcceptedPlugin[] } {
return util.isPlainObject(v) && util.isArray(v.plugins)
}