-
Notifications
You must be signed in to change notification settings - Fork 1
/
index.js
164 lines (141 loc) · 5.46 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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
/**
* @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 {boolean} hash
* OPTIONAL: defaults to false.
* Adds the compilation hash to the filename. You can either choose within the filename
* where the hash is inserted by adding `[hash]` i.e. `test.[hash].js` or the hash will be
* appended to the end of the file i.e. `test.js?hash`.
*
* @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 string via `.toString` (if the function was overridden) or `JSON.stringify`.
*/
/**
* 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) {
const hash = compilation.hash || "";
if (filename.includes("[hash]")) {
filename = filename.replace("[hash]", hash);
} else if (hash) {
filename = `${filename}?${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
: contentToSource(content);
if (version < 5) {
compilation.assets[relativeOutputPath] = source;
} else {
compilation.emitAsset(relativeOutputPath, source);
}
resolve();
});
}
function contentToSource(content) {
if (content === undefined || content === null) {
content = "";
} else if (typeof content !== "string" && !(content instanceof Buffer)) {
const hasItsOwnToString = typeof content.toString === "function"
&& content.toString !== Object.prototype.toString
&& content.toString !== Array.prototype.toString;
if (hasItsOwnToString) {
content = content.toString();
} else {
content = JSON.stringify(content);
}
}
return new RawSource(content);
}
module.exports = EmitFilePlugin;