[6a3a178] | 1 | /*
|
---|
| 2 | MIT License http://www.opensource.org/licenses/mit-license.php
|
---|
| 3 | Author Sergey Melyukov @smelukov
|
---|
| 4 | */
|
---|
| 5 |
|
---|
| 6 | "use strict";
|
---|
| 7 |
|
---|
| 8 | const mimeTypes = require("mime-types");
|
---|
| 9 | const path = require("path");
|
---|
| 10 | const { RawSource } = require("webpack-sources");
|
---|
| 11 | const Generator = require("../Generator");
|
---|
| 12 | const RuntimeGlobals = require("../RuntimeGlobals");
|
---|
| 13 | const createHash = require("../util/createHash");
|
---|
| 14 | const { makePathsRelative } = require("../util/identifier");
|
---|
| 15 |
|
---|
| 16 | /** @typedef {import("webpack-sources").Source} Source */
|
---|
| 17 | /** @typedef {import("../../declarations/WebpackOptions").AssetGeneratorOptions} AssetGeneratorOptions */
|
---|
| 18 | /** @typedef {import("../../declarations/WebpackOptions").RawPublicPath} RawPublicPath */
|
---|
| 19 | /** @typedef {import("../Compilation")} Compilation */
|
---|
| 20 | /** @typedef {import("../Compiler")} Compiler */
|
---|
| 21 | /** @typedef {import("../Generator").GenerateContext} GenerateContext */
|
---|
| 22 | /** @typedef {import("../Generator").UpdateHashContext} UpdateHashContext */
|
---|
| 23 | /** @typedef {import("../Module")} Module */
|
---|
| 24 | /** @typedef {import("../NormalModule")} NormalModule */
|
---|
| 25 | /** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */
|
---|
| 26 | /** @typedef {import("../util/Hash")} Hash */
|
---|
| 27 |
|
---|
| 28 | const mergeMaybeArrays = (a, b) => {
|
---|
| 29 | const set = new Set();
|
---|
| 30 | if (Array.isArray(a)) for (const item of a) set.add(item);
|
---|
| 31 | else set.add(a);
|
---|
| 32 | if (Array.isArray(b)) for (const item of b) set.add(item);
|
---|
| 33 | else set.add(b);
|
---|
| 34 | return Array.from(set);
|
---|
| 35 | };
|
---|
| 36 |
|
---|
| 37 | const mergeAssetInfo = (a, b) => {
|
---|
| 38 | const result = { ...a, ...b };
|
---|
| 39 | for (const key of Object.keys(a)) {
|
---|
| 40 | if (key in b) {
|
---|
| 41 | if (a[key] === b[key]) continue;
|
---|
| 42 | switch (key) {
|
---|
| 43 | case "fullhash":
|
---|
| 44 | case "chunkhash":
|
---|
| 45 | case "modulehash":
|
---|
| 46 | case "contenthash":
|
---|
| 47 | result[key] = mergeMaybeArrays(a[key], b[key]);
|
---|
| 48 | break;
|
---|
| 49 | case "immutable":
|
---|
| 50 | case "development":
|
---|
| 51 | case "hotModuleReplacement":
|
---|
| 52 | case "javascriptModule ":
|
---|
| 53 | result[key] = a[key] || b[key];
|
---|
| 54 | break;
|
---|
| 55 | case "related":
|
---|
| 56 | result[key] = mergeRelatedInfo(a[key], b[key]);
|
---|
| 57 | break;
|
---|
| 58 | default:
|
---|
| 59 | throw new Error(`Can't handle conflicting asset info for ${key}`);
|
---|
| 60 | }
|
---|
| 61 | }
|
---|
| 62 | }
|
---|
| 63 | return result;
|
---|
| 64 | };
|
---|
| 65 |
|
---|
| 66 | const mergeRelatedInfo = (a, b) => {
|
---|
| 67 | const result = { ...a, ...b };
|
---|
| 68 | for (const key of Object.keys(a)) {
|
---|
| 69 | if (key in b) {
|
---|
| 70 | if (a[key] === b[key]) continue;
|
---|
| 71 | result[key] = mergeMaybeArrays(a[key], b[key]);
|
---|
| 72 | }
|
---|
| 73 | }
|
---|
| 74 | return result;
|
---|
| 75 | };
|
---|
| 76 |
|
---|
| 77 | const JS_TYPES = new Set(["javascript"]);
|
---|
| 78 | const JS_AND_ASSET_TYPES = new Set(["javascript", "asset"]);
|
---|
| 79 |
|
---|
| 80 | class AssetGenerator extends Generator {
|
---|
| 81 | /**
|
---|
| 82 | * @param {AssetGeneratorOptions["dataUrl"]=} dataUrlOptions the options for the data url
|
---|
| 83 | * @param {string=} filename override for output.assetModuleFilename
|
---|
| 84 | * @param {RawPublicPath=} publicPath override for output.assetModulePublicPath
|
---|
| 85 | * @param {boolean=} emit generate output asset
|
---|
| 86 | */
|
---|
| 87 | constructor(dataUrlOptions, filename, publicPath, emit) {
|
---|
| 88 | super();
|
---|
| 89 | this.dataUrlOptions = dataUrlOptions;
|
---|
| 90 | this.filename = filename;
|
---|
| 91 | this.publicPath = publicPath;
|
---|
| 92 | this.emit = emit;
|
---|
| 93 | }
|
---|
| 94 |
|
---|
| 95 | /**
|
---|
| 96 | * @param {NormalModule} module module for which the code should be generated
|
---|
| 97 | * @param {GenerateContext} generateContext context for generate
|
---|
| 98 | * @returns {Source} generated code
|
---|
| 99 | */
|
---|
| 100 | generate(
|
---|
| 101 | module,
|
---|
| 102 | { runtime, chunkGraph, runtimeTemplate, runtimeRequirements, type, getData }
|
---|
| 103 | ) {
|
---|
| 104 | switch (type) {
|
---|
| 105 | case "asset":
|
---|
| 106 | return module.originalSource();
|
---|
| 107 | default: {
|
---|
| 108 | runtimeRequirements.add(RuntimeGlobals.module);
|
---|
| 109 |
|
---|
| 110 | const originalSource = module.originalSource();
|
---|
| 111 | if (module.buildInfo.dataUrl) {
|
---|
| 112 | let encodedSource;
|
---|
| 113 | if (typeof this.dataUrlOptions === "function") {
|
---|
| 114 | encodedSource = this.dataUrlOptions.call(
|
---|
| 115 | null,
|
---|
| 116 | originalSource.source(),
|
---|
| 117 | {
|
---|
| 118 | filename: module.matchResource || module.resource,
|
---|
| 119 | module
|
---|
| 120 | }
|
---|
| 121 | );
|
---|
| 122 | } else {
|
---|
| 123 | /** @type {string | false | undefined} */
|
---|
| 124 | let encoding = this.dataUrlOptions.encoding;
|
---|
| 125 | if (encoding === undefined) {
|
---|
| 126 | if (
|
---|
| 127 | module.resourceResolveData &&
|
---|
| 128 | module.resourceResolveData.encoding !== undefined
|
---|
| 129 | ) {
|
---|
| 130 | encoding = module.resourceResolveData.encoding;
|
---|
| 131 | }
|
---|
| 132 | }
|
---|
| 133 | if (encoding === undefined) {
|
---|
| 134 | encoding = "base64";
|
---|
| 135 | }
|
---|
| 136 | let ext;
|
---|
| 137 | let mimeType = this.dataUrlOptions.mimetype;
|
---|
| 138 | if (mimeType === undefined) {
|
---|
| 139 | ext = path.extname(module.nameForCondition());
|
---|
| 140 | if (
|
---|
| 141 | module.resourceResolveData &&
|
---|
| 142 | module.resourceResolveData.mimetype !== undefined
|
---|
| 143 | ) {
|
---|
| 144 | mimeType =
|
---|
| 145 | module.resourceResolveData.mimetype +
|
---|
| 146 | module.resourceResolveData.parameters;
|
---|
| 147 | } else if (ext) {
|
---|
| 148 | mimeType = mimeTypes.lookup(ext);
|
---|
| 149 | }
|
---|
| 150 | }
|
---|
| 151 | if (typeof mimeType !== "string") {
|
---|
| 152 | throw new Error(
|
---|
| 153 | "DataUrl can't be generated automatically, " +
|
---|
| 154 | `because there is no mimetype for "${ext}" in mimetype database. ` +
|
---|
| 155 | 'Either pass a mimetype via "generator.mimetype" or ' +
|
---|
| 156 | 'use type: "asset/resource" to create a resource file instead of a DataUrl'
|
---|
| 157 | );
|
---|
| 158 | }
|
---|
| 159 |
|
---|
| 160 | let encodedContent;
|
---|
| 161 | if (
|
---|
| 162 | module.resourceResolveData &&
|
---|
| 163 | module.resourceResolveData.encoding === encoding
|
---|
| 164 | ) {
|
---|
| 165 | encodedContent = module.resourceResolveData.encodedContent;
|
---|
| 166 | } else {
|
---|
| 167 | switch (encoding) {
|
---|
| 168 | case "base64": {
|
---|
| 169 | encodedContent = originalSource.buffer().toString("base64");
|
---|
| 170 | break;
|
---|
| 171 | }
|
---|
| 172 | case false: {
|
---|
| 173 | const content = originalSource.source();
|
---|
| 174 |
|
---|
| 175 | if (typeof content !== "string") {
|
---|
| 176 | encodedContent = content.toString("utf-8");
|
---|
| 177 | }
|
---|
| 178 |
|
---|
| 179 | encodedContent = encodeURIComponent(encodedContent).replace(
|
---|
| 180 | /[!'()*]/g,
|
---|
| 181 | character => "%" + character.codePointAt(0).toString(16)
|
---|
| 182 | );
|
---|
| 183 | break;
|
---|
| 184 | }
|
---|
| 185 | default:
|
---|
| 186 | throw new Error(`Unsupported encoding '${encoding}'`);
|
---|
| 187 | }
|
---|
| 188 | }
|
---|
| 189 |
|
---|
| 190 | encodedSource = `data:${mimeType}${
|
---|
| 191 | encoding ? `;${encoding}` : ""
|
---|
| 192 | },${encodedContent}`;
|
---|
| 193 | }
|
---|
| 194 | return new RawSource(
|
---|
| 195 | `${RuntimeGlobals.module}.exports = ${JSON.stringify(
|
---|
| 196 | encodedSource
|
---|
| 197 | )};`
|
---|
| 198 | );
|
---|
| 199 | } else {
|
---|
| 200 | const assetModuleFilename =
|
---|
| 201 | this.filename || runtimeTemplate.outputOptions.assetModuleFilename;
|
---|
| 202 | const hash = createHash(runtimeTemplate.outputOptions.hashFunction);
|
---|
| 203 | if (runtimeTemplate.outputOptions.hashSalt) {
|
---|
| 204 | hash.update(runtimeTemplate.outputOptions.hashSalt);
|
---|
| 205 | }
|
---|
| 206 | hash.update(originalSource.buffer());
|
---|
| 207 | const fullHash = /** @type {string} */ (
|
---|
| 208 | hash.digest(runtimeTemplate.outputOptions.hashDigest)
|
---|
| 209 | );
|
---|
| 210 | const contentHash = fullHash.slice(
|
---|
| 211 | 0,
|
---|
| 212 | runtimeTemplate.outputOptions.hashDigestLength
|
---|
| 213 | );
|
---|
| 214 | module.buildInfo.fullContentHash = fullHash;
|
---|
| 215 | const sourceFilename = makePathsRelative(
|
---|
| 216 | runtimeTemplate.compilation.compiler.context,
|
---|
| 217 | module.matchResource || module.resource,
|
---|
| 218 | runtimeTemplate.compilation.compiler.root
|
---|
| 219 | ).replace(/^\.\//, "");
|
---|
| 220 | let { path: filename, info: assetInfo } =
|
---|
| 221 | runtimeTemplate.compilation.getAssetPathWithInfo(
|
---|
| 222 | assetModuleFilename,
|
---|
| 223 | {
|
---|
| 224 | module,
|
---|
| 225 | runtime,
|
---|
| 226 | filename: sourceFilename,
|
---|
| 227 | chunkGraph,
|
---|
| 228 | contentHash
|
---|
| 229 | }
|
---|
| 230 | );
|
---|
| 231 | let publicPath;
|
---|
| 232 | if (this.publicPath !== undefined) {
|
---|
| 233 | const { path, info } =
|
---|
| 234 | runtimeTemplate.compilation.getAssetPathWithInfo(
|
---|
| 235 | this.publicPath,
|
---|
| 236 | {
|
---|
| 237 | module,
|
---|
| 238 | runtime,
|
---|
| 239 | filename: sourceFilename,
|
---|
| 240 | chunkGraph,
|
---|
| 241 | contentHash
|
---|
| 242 | }
|
---|
| 243 | );
|
---|
| 244 | publicPath = JSON.stringify(path);
|
---|
| 245 | assetInfo = mergeAssetInfo(assetInfo, info);
|
---|
| 246 | } else {
|
---|
| 247 | publicPath = RuntimeGlobals.publicPath;
|
---|
| 248 | runtimeRequirements.add(RuntimeGlobals.publicPath); // add __webpack_require__.p
|
---|
| 249 | }
|
---|
| 250 | assetInfo = {
|
---|
| 251 | sourceFilename,
|
---|
| 252 | ...assetInfo
|
---|
| 253 | };
|
---|
| 254 | module.buildInfo.filename = filename;
|
---|
| 255 | module.buildInfo.assetInfo = assetInfo;
|
---|
| 256 | if (getData) {
|
---|
| 257 | // Due to code generation caching module.buildInfo.XXX can't used to store such information
|
---|
| 258 | // It need to be stored in the code generation results instead, where it's cached too
|
---|
| 259 | // TODO webpack 6 For back-compat reasons we also store in on module.buildInfo
|
---|
| 260 | const data = getData();
|
---|
| 261 | data.set("fullContentHash", fullHash);
|
---|
| 262 | data.set("filename", filename);
|
---|
| 263 | data.set("assetInfo", assetInfo);
|
---|
| 264 | }
|
---|
| 265 |
|
---|
| 266 | return new RawSource(
|
---|
| 267 | `${
|
---|
| 268 | RuntimeGlobals.module
|
---|
| 269 | }.exports = ${publicPath} + ${JSON.stringify(filename)};`
|
---|
| 270 | );
|
---|
| 271 | }
|
---|
| 272 | }
|
---|
| 273 | }
|
---|
| 274 | }
|
---|
| 275 |
|
---|
| 276 | /**
|
---|
| 277 | * @param {NormalModule} module fresh module
|
---|
| 278 | * @returns {Set<string>} available types (do not mutate)
|
---|
| 279 | */
|
---|
| 280 | getTypes(module) {
|
---|
| 281 | if ((module.buildInfo && module.buildInfo.dataUrl) || this.emit === false) {
|
---|
| 282 | return JS_TYPES;
|
---|
| 283 | } else {
|
---|
| 284 | return JS_AND_ASSET_TYPES;
|
---|
| 285 | }
|
---|
| 286 | }
|
---|
| 287 |
|
---|
| 288 | /**
|
---|
| 289 | * @param {NormalModule} module the module
|
---|
| 290 | * @param {string=} type source type
|
---|
| 291 | * @returns {number} estimate size of the module
|
---|
| 292 | */
|
---|
| 293 | getSize(module, type) {
|
---|
| 294 | switch (type) {
|
---|
| 295 | case "asset": {
|
---|
| 296 | const originalSource = module.originalSource();
|
---|
| 297 |
|
---|
| 298 | if (!originalSource) {
|
---|
| 299 | return 0;
|
---|
| 300 | }
|
---|
| 301 |
|
---|
| 302 | return originalSource.size();
|
---|
| 303 | }
|
---|
| 304 | default:
|
---|
| 305 | if (module.buildInfo && module.buildInfo.dataUrl) {
|
---|
| 306 | const originalSource = module.originalSource();
|
---|
| 307 |
|
---|
| 308 | if (!originalSource) {
|
---|
| 309 | return 0;
|
---|
| 310 | }
|
---|
| 311 |
|
---|
| 312 | // roughly for data url
|
---|
| 313 | // Example: m.exports="data:image/png;base64,ag82/f+2=="
|
---|
| 314 | // 4/3 = base64 encoding
|
---|
| 315 | // 34 = ~ data url header + footer + rounding
|
---|
| 316 | return originalSource.size() * 1.34 + 36;
|
---|
| 317 | } else {
|
---|
| 318 | // it's only estimated so this number is probably fine
|
---|
| 319 | // Example: m.exports=r.p+"0123456789012345678901.ext"
|
---|
| 320 | return 42;
|
---|
| 321 | }
|
---|
| 322 | }
|
---|
| 323 | }
|
---|
| 324 |
|
---|
| 325 | /**
|
---|
| 326 | * @param {Hash} hash hash that will be modified
|
---|
| 327 | * @param {UpdateHashContext} updateHashContext context for updating hash
|
---|
| 328 | */
|
---|
| 329 | updateHash(hash, { module }) {
|
---|
| 330 | hash.update(module.buildInfo.dataUrl ? "data-url" : "resource");
|
---|
| 331 | }
|
---|
| 332 | }
|
---|
| 333 |
|
---|
| 334 | module.exports = AssetGenerator;
|
---|