-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.ts
85 lines (76 loc) · 2.6 KB
/
index.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
import { Buffer } from 'buffer'
import path from 'path'
import { Compilation, sources, WebpackPluginFunction } from 'webpack'
import RawSource = sources.RawSource
export type RawContent = string | Buffer
/** see https://webpack.js.org/api/compilation-hooks/#list-of-asset-processing-stages */
export type Stage =
| 'additional'
| 'pre_process'
| 'derived'
| 'additions'
| 'optimize'
| 'optimize_count'
| 'optimize_compatibility'
| 'optimize_size'
| 'dev_tooling'
| 'optimize_inline'
| 'summarize'
| 'optimize_hash'
| 'optimize_transfer'
| 'analyse'
| 'report'
export interface Options {
disabled?: boolean
name: string | string[] | ((hash: Compilation['hash']) => string | string[])
content: RawContent | ((assets: Compilation['assets']) => PromiseLike<RawContent> | RawContent)
stage?: Stage
}
export const emitFile = (options: Options): WebpackPluginFunction => {
const pluginName = 'EmitFileWebpackPlugin'
const stage = (options.stage ?? 'additional').toUpperCase() as Uppercase<Stage>
const tapOptions = { name: pluginName, stage: Compilation[`PROCESS_ASSETS_STAGE_${stage}`] }
return (compiler) => {
if (options.disabled) {
return
}
compiler.hooks.thisCompilation.tap(pluginName, (compilation) => {
const filePath = resolveFilePath(compilation.options.output.path ?? '', options.name, compilation.hash)
compilation.hooks.processAssets.tapPromise(tapOptions, async () => {
const source = await toSource(options.content, compilation.assets)
if (source !== null) {
compilation.emitAsset(filePath, source)
}
})
})
}
}
export interface JSONOptions<T> extends Omit<Options, 'content'> {
content: T | { toJSON(): T }
space?: number
}
export const emitJSONFile = <T>(options: JSONOptions<T>) => {
return emitFile({
disabled: options.disabled,
name: options.name,
content: JSON.stringify(options.content, null, options.space ?? 2),
stage: options.stage,
})
}
export default emitFile
async function toSource(input: Options['content'], assets: Compilation['assets']): Promise<RawSource | null> {
if (typeof input === 'string' || Buffer.isBuffer(input)) {
return new RawSource(input)
} else if (typeof input === 'function') {
return toSource(await input(assets), assets)
}
return null
}
function resolveFilePath(basePath: string, name: Options['name'], hash?: string) {
name = typeof name === 'function' ? name(hash) : name
name = Array.isArray(name) ? path.join(...name) : name
if (path.isAbsolute(name)) {
return name
}
return path.relative(basePath, path.resolve(basePath, name))
}