forked from Kir-Antipov/emit-file-webpack-plugin
-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
138 lines (118 loc) · 4.59 KB
/
index.js
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
135
136
137
138
/**
* @author Kir_Antipov
* See LICENSE.md file in root directory for full license.
*/
"use strict";
const { Buffer } = require("buffer");
const path = require("path");
const webpack = require("webpack");
const version = +webpack.version.split(".")[0];
// Webpack 5 exposes the sources property to ensure the right version of webpack-sources is used.
// require('webpack-sources') approach may result in the "Cannot find module 'webpack-sources'" error.
const { Source, RawSource } = webpack.sources || require("webpack-sources");
/**
* @typedef {object} EmitFilePluginOptions
*
* @property {string} path
* OPTIONAL: defaults to the Webpack output path.
* Output path.
* Can be relative (to Webpack output path) or absolute.
*
* @property {string} filename
* REQUIRED.
* Name of the file to add to assets.
*
* @property {number} stage
* OPTIONAL: defaults to the webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL.
* Asset processing stage.
*
* @property {string|Buffer|Source|((assets: Record<string, Source>) => (string|Buffer|Source))|((assets: Record<string, Source>) => (Promise<string|Buffer|Source>))} content
* REQUIRED.
* File content. Can be either a string, a buffer, or a (asynchronous) function.
* If the resulting object is not a string or a buffer, it will be converted to JSON.
*/
/**
* Webpack plugin to emit files.
*
* @param {EmitFilePluginOptions} options The EmitFilePlugin config.
*/
function EmitFilePlugin(options) {
if (!options) {
throw new Error(`${EmitFilePlugin.name}: Please provide 'options' for the ${EmitFilePlugin.name} config.`);
}
if (!options.filename) {
throw new Error(`${EmitFilePlugin.name}: Please provide 'options.filename' in the ${EmitFilePlugin.name} config.`);
}
if (!options.content && options.content !== "") {
throw new Error(`${EmitFilePlugin.name}: Please provide 'options.content' in the ${EmitFilePlugin.name} config.`);
}
if (typeof options.stage == "number" && version < 5) {
console.warn(`${EmitFilePlugin.name}: 'options.stage' is only available for Webpack version 5 and higher.`);
}
this.options = options;
}
/**
* Plugin entry point.
*
* @param {webpack.Compiler} compiler The compiler.
*/
EmitFilePlugin.prototype.apply = function (compiler) {
if (version < 4) {
compiler.plugin("emit", (compilation, callback) => emitFile(this.options, compilation, callback, callback));
} else if (version == 4) {
compiler.hooks.emit.tapAsync(EmitFilePlugin.name, (compilation, callback) => emitFile(this.options, compilation, callback, callback));
} else {
compiler.hooks.thisCompilation.tap(EmitFilePlugin.name, compilation => {
compilation.hooks.processAssets.tapPromise(
{
name: EmitFilePlugin.name,
stage: typeof this.options.stage == "number" ? this.options.stage : webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL,
},
() => new Promise((resolve, reject) => emitFile(this.options, compilation, resolve, reject))
);
});
}
};
/**
* @param {EmitFilePluginOptions} options
* @param {webpack.Compilation} compilation
* @param {() => void} resolve
*/
function emitFile(options, compilation, resolve) {
const outputPath = options.path || compilation.options.output.path;
let filename = options.filename;
if (options.hash) {
if (options.filename.includes('[hash]')) {
filename = options.filename.replace('[hash]', compilation.hash);
} else {
filename = `${options.filename}?${compilation.hash}`;
}
}
const outputPathAndFilename = path.resolve(
compilation.options.output.path,
outputPath,
filename
);
const relativeOutputPath = path.relative(
compilation.options.output.path,
outputPathAndFilename
);
const contentOrPromise = typeof options.content == "function"
? options.content(compilation.assets)
: options.content;
const contentPromise = contentOrPromise instanceof Promise
? contentOrPromise
: new Promise(resolve => resolve(contentOrPromise));
contentPromise.then(content => {
const source = content instanceof Source
? content
: new RawSource((typeof content == "string" || content instanceof Buffer) ? content : JSON.stringify(content));
if (version < 5) {
compilation.assets[relativeOutputPath] = source;
} else {
compilation.emitAsset(relativeOutputPath, source);
}
resolve();
});
}
module.exports = EmitFilePlugin;