move data url condition to parser

avoid calling dataUrl function repeated
remove cache to avoid memory leak
include data url decision in module hash
refactor code for readablility
This commit is contained in:
Tobias Koppers 2019-11-26 13:56:27 +01:00
parent a04f7bcafd
commit 317da38171
22 changed files with 455 additions and 280 deletions

View File

@ -1,41 +0,0 @@
/**
* This file was automatically generated.
* DO NOT MODIFY BY HAND.
* Run `yarn special-lint-fix` to update
*/
/**
* Function that executes for module source abd resource and should return new module source
*
* This interface was referenced by `AssetModulesPluginOptions`'s JSON-Schema
* via the `definition` "DataUrlFn".
*/
export type DataUrlFn = (
source: string | Buffer,
resourcePath: string
) => string | null;
export interface AssetModulesPluginOptions {
/**
* The options for data url generator
*/
dataUrl?: false | DataUrlOptions | DataUrlFn;
}
/**
* This interface was referenced by `AssetModulesPluginOptions`'s JSON-Schema
* via the `definition` "DataUrlOptions".
*/
export interface DataUrlOptions {
/**
* Module output encoding
*/
encoding?: false | "base64";
/**
* Maximum size of files that should be inline as modules. Default: 8kb
*/
maxSize?: number;
/**
* Module output mimetype (getting from file ext by default)
*/
mimetype?: string;
}

View File

@ -0,0 +1,37 @@
/**
* This file was automatically generated.
* DO NOT MODIFY BY HAND.
* Run `yarn special-lint-fix` to update
*/
/**
* Function that executes for module and should return an DataUrl string
*
* This interface was referenced by `AssetModulesPluginGeneratorOptions`'s JSON-Schema
* via the `definition` "DataUrlFunction".
*/
export type DataUrlFunction = (
source: string | Buffer,
context: {filename: string; module: import("../../lib/Module")}
) => string;
export interface AssetModulesPluginGeneratorOptions {
/**
* The options for data url generator
*/
dataUrl?: DataUrlOptions | DataUrlFunction;
}
/**
* This interface was referenced by `AssetModulesPluginGeneratorOptions`'s JSON-Schema
* via the `definition` "DataUrlOptions".
*/
export interface DataUrlOptions {
/**
* Asset encoding (defaults to base64)
*/
encoding?: false | "base64";
/**
* Asset mimetype (getting from file extension by default)
*/
mimetype?: string;
}

View File

@ -0,0 +1,33 @@
/**
* This file was automatically generated.
* DO NOT MODIFY BY HAND.
* Run `yarn special-lint-fix` to update
*/
/**
* Function that executes for module and should return whenever asset should be inlined as DataUrl
*
* This interface was referenced by `AssetModulesPluginParserOptions`'s JSON-Schema
* via the `definition` "DataUrlFunction".
*/
export type DataUrlFunction = (
source: string | Buffer,
context: {filename: string; module: import("../../lib/Module")}
) => boolean;
export interface AssetModulesPluginParserOptions {
/**
* The condition for inlining the asset as DataUrl
*/
dataUrlCondition?: DataUrlOptions | DataUrlFunction;
}
/**
* This interface was referenced by `AssetModulesPluginParserOptions`'s JSON-Schema
* via the `definition` "DataUrlOptions".
*/
export interface DataUrlOptions {
/**
* Maximum size of asset that should be inline as modules. Default: 8kb
*/
maxSize?: number;
}

View File

@ -200,13 +200,13 @@ Version: webpack 5.0.0-beta.7
Asset Size
output.js 4.01 KiB [emitted] [name: main]
Entrypoint main = output.js
chunk output.js (main) 1.6 KiB (javascript) 274 bytes (runtime) [entry] [rendered]
chunk output.js (main) 1.54 KiB (javascript) 274 bytes (runtime) [entry] [rendered]
> ./example.js main
./example.js 658 bytes [built]
[no exports]
[used exports unknown]
entry ./example.js main
./images/file.svg 984 bytes [built]
./images/file.svg 915 bytes [built]
[no exports]
[used exports unknown]
harmony side effect evaluation ./images/file.svg ./example.js 1:0-36

View File

@ -119,7 +119,7 @@ function createImageElement(title, src) {
/*! runtime requirements: module, __webpack_require__.p, __webpack_require__.* */
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
module.exports = __webpack_require__.p + "images/0985f24c5584e5f04e32.png";
module.exports = __webpack_require__.p + "images/151cfcfa1bd74779aadb.png";
/***/ }),
/* 2 */
@ -220,16 +220,16 @@ module.exports = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDo...vc3ZnPgo="
Hash: 0a1b2c3d4e5f6a7b8c9d
Version: webpack 5.0.0-beta.7
Asset Size
images/0985f24c5584e5f04e32.png 14.6 KiB [emitted] [immutable] [name: (main)]
images/151cfcfa1bd74779aadb.png 14.6 KiB [emitted] [immutable] [name: (main)]
output.js 13.4 KiB [emitted] [name: main]
Entrypoint main = output.js (images/0985f24c5584e5f04e32.png)
chunk output.js (main) 10.6 KiB (javascript) 14.6 KiB (asset) 306 bytes (runtime) [entry] [rendered]
Entrypoint main = output.js (images/151cfcfa1bd74779aadb.png)
chunk output.js (main) 9.58 KiB (javascript) 14.6 KiB (asset) 306 bytes (runtime) [entry] [rendered]
> ./example.js main
./example.js 742 bytes [built]
[no exports]
[used exports unknown]
entry ./example.js main
./images/file.jpg 8.83 KiB [built]
./images/file.jpg 7.92 KiB [built]
[no exports]
[used exports unknown]
harmony side effect evaluation ./images/file.jpg ./example.js 2:0-36
@ -239,7 +239,7 @@ chunk output.js (main) 10.6 KiB (javascript) 14.6 KiB (asset) 306 bytes (runtime
[used exports unknown]
harmony side effect evaluation ./images/file.png ./example.js 1:0-36
harmony import specifier ./images/file.png ./example.js 28:1-4
./images/file.svg 984 bytes [built]
./images/file.svg 915 bytes [built]
[no exports]
[used exports unknown]
harmony side effect evaluation ./images/file.svg ./example.js 3:0-36

View File

@ -9,11 +9,13 @@ const AbstractMethodError = require("./AbstractMethodError");
/** @typedef {import("webpack-sources").Source} Source */
/** @typedef {import("./ChunkGraph")} ChunkGraph */
/** @typedef {import("./Compilation")} Compilation */
/** @typedef {import("./DependencyTemplate")} DependencyTemplate */
/** @typedef {import("./DependencyTemplates")} DependencyTemplates */
/** @typedef {import("./ModuleGraph")} ModuleGraph */
/** @typedef {import("./NormalModule")} NormalModule */
/** @typedef {import("./RuntimeTemplate")} RuntimeTemplate */
/** @typedef {import("./util/Hash")} Hash */
/**
* @typedef {Object} GenerateContext
@ -25,6 +27,12 @@ const AbstractMethodError = require("./AbstractMethodError");
* @property {string} type which kind of code should be generated
*/
/**
* @typedef {Object} UpdateHashContext
* @property {NormalModule} module the module
* @property {Compilation} compilation the compilation
*/
/**
*
*/
@ -64,6 +72,14 @@ class Generator {
) {
throw new AbstractMethodError();
}
/**
* @param {Hash} hash hash that will be modified
* @param {UpdateHashContext} updateHashContext context for updating hash
*/
updateHash(hash, { module, compilation }) {
// no nothing
}
}
class ByTypeGenerator extends Generator {

View File

@ -651,6 +651,12 @@ class NormalModule extends Module {
}
hash.update("meta");
hash.update(JSON.stringify(this.buildMeta));
if (this.generator.updateHash) {
this.generator.updateHash(hash, {
module: this,
compilation
});
}
this.buildInfo.hash = /** @type {string} */ (hash.digest("hex"));
}

View File

@ -12,33 +12,15 @@ const Generator = require("../Generator");
const RuntimeGlobals = require("../RuntimeGlobals");
/** @typedef {import("webpack-sources").Source} Source */
/** @typedef {import("../../declarations/plugins/AssetModulesPlugin").AssetModulesPluginOptions} AssetModulesPluginOptions */
/** @typedef {import("../../declarations/plugins/AssetModulesPluginGenerator").AssetModulesPluginGeneratorOptions} AssetModulesPluginGeneratorOptions */
/** @typedef {import("../Compilation")} Compilation */
/** @typedef {import("../Compiler")} Compiler */
/** @typedef {import("../Generator").GenerateContext} GenerateContext */
/** @typedef {import("../Generator").UpdateHashContext} UpdateHashContext */
/** @typedef {import("../Module")} Module */
/** @typedef {import("../NormalModule")} NormalModule */
/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */
/**
* @param {NormalModule} module the module
* @param {AssetModulesPluginOptions} options the options to encode
* @returns {boolean} should emit additional asset for the module
*/
const shouldEmitAsset = (module, options) => {
const originalSource = module.originalSource();
if (typeof options.dataUrl === "function") {
return (
options.dataUrl.call(null, module, module.nameForCondition()) === false
);
}
if (options.dataUrl === false) {
return true;
}
return originalSource.size() > options.dataUrl.maxSize;
};
/** @typedef {import("../util/Hash")} Hash */
const JS_TYPES = new Set(["javascript"]);
const JS_AND_ASSET_TYPES = new Set(["javascript", "asset"]);
@ -46,12 +28,12 @@ const JS_AND_ASSET_TYPES = new Set(["javascript", "asset"]);
class AssetGenerator extends Generator {
/**
* @param {Compilation} compilation the compilation
* @param {AssetModulesPluginOptions} options the options
* @param {AssetModulesPluginGeneratorOptions["dataUrl"]=} dataUrlOptions the options for the data url
*/
constructor(compilation, options) {
constructor(compilation, dataUrlOptions) {
super();
this.compilation = compilation;
this.options = options;
this.dataUrlOptions = dataUrlOptions;
}
/**
@ -60,63 +42,78 @@ class AssetGenerator extends Generator {
* @returns {Source} generated code
*/
generate(module, { chunkGraph, runtimeTemplate, runtimeRequirements, type }) {
if (type === "asset") {
return module.originalSource();
}
switch (type) {
case "asset":
return module.originalSource();
default: {
runtimeRequirements.add(RuntimeGlobals.module);
runtimeRequirements.add(RuntimeGlobals.module);
if (module.buildInfo.dataUrl) {
const originalSource = module.originalSource();
if (!shouldEmitAsset(module, this.options)) {
const originalSource = module.originalSource();
let content = originalSource.source();
let encodedSource;
if (typeof this.dataUrlOptions === "function") {
encodedSource = this.dataUrlOptions.call(
null,
originalSource.source(),
{
filename: module.nameForCondition(),
module
}
);
} else {
const encoding = this.dataUrlOptions.encoding;
const mimeType =
this.dataUrlOptions.mimetype ||
mime.getType(path.extname(module.nameForCondition()));
let encodedSource;
if (typeof this.options.dataUrl === "function") {
encodedSource = this.options.dataUrl.call(
null,
content,
module.nameForCondition()
);
} else {
// @ts-ignore non-false dataUrl ensures in shouldEmitAsset above
const encoding = this.options.dataUrl.encoding;
const extname = path.extname(module.nameForCondition());
// @ts-ignore non-false dataUrl ensures in shouldEmitAsset above
const mimeType = this.options.dataUrl.mimetype || mime.getType(extname);
let encodedContent;
switch (encoding) {
case "base64": {
encodedContent = originalSource.buffer().toString("base64");
break;
}
case false: {
const content = originalSource.source();
if (typeof content === "string") {
encodedContent = encodeURI(content);
} else {
encodedContent = encodeURI(content.toString("utf-8"));
}
break;
}
default:
throw new Error(`Unsupported encoding '${encoding}'`);
}
if (encoding === "base64") {
if (typeof content === "string") {
content = Buffer.from(content);
encodedSource = `data:${mimeType}${
encoding ? `;${encoding}` : ""
},${encodedContent}`;
}
return new RawSource(
`${RuntimeGlobals.module}.exports = ${JSON.stringify(
encodedSource
)};`
);
} else {
const filename = module.nameForCondition();
const { assetModuleFilename } = runtimeTemplate.outputOptions;
const url = this.compilation.getAssetPath(assetModuleFilename, {
module,
filename,
chunkGraph
});
content = content.toString("base64");
runtimeRequirements.add(RuntimeGlobals.publicPath); // add __webpack_require__.p
return new RawSource(
`${RuntimeGlobals.module}.exports = ${
RuntimeGlobals.publicPath
} + ${JSON.stringify(url)};`
);
}
encodedSource = `data:${mimeType}${
encoding ? `;${encoding}` : ""
},${content}`;
}
return new RawSource(
`${RuntimeGlobals.module}.exports = ${JSON.stringify(encodedSource)};`
);
}
const filename = module.nameForCondition();
const { assetModuleFilename } = runtimeTemplate.outputOptions;
const url = this.compilation.getAssetPath(assetModuleFilename, {
module,
filename,
chunkGraph
});
runtimeRequirements.add(RuntimeGlobals.publicPath); // add __webpack_require__.p
// TODO: (hiroppy) use ESM
return new RawSource(
`${RuntimeGlobals.module}.exports = ${
RuntimeGlobals.publicPath
} + ${JSON.stringify(url)};`
);
}
/**
@ -124,11 +121,11 @@ class AssetGenerator extends Generator {
* @returns {Set<string>} available types (do not mutate)
*/
getTypes(module) {
if (shouldEmitAsset(module, this.options)) {
if (module.buildInfo.dataUrl) {
return JS_TYPES;
} else {
return JS_AND_ASSET_TYPES;
}
return JS_TYPES;
}
/**
@ -137,25 +134,44 @@ class AssetGenerator extends Generator {
* @returns {number} estimate size of the module
*/
getSize(module, type = module.type) {
const originalSource = module.originalSource();
switch (type) {
case "asset": {
const originalSource = module.originalSource();
if (!originalSource) {
return 0;
}
if (!originalSource) {
return 0;
}
if (type === "asset") {
return originalSource.size();
}
return originalSource.size();
}
default:
if (module.buildInfo.dataUrl) {
const originalSource = module.originalSource();
if (shouldEmitAsset(module, this.options)) {
// it's only estimated so this number is probably fine
// Example: m.exports=r.p+"0123456789012345678901.ext"
return 42;
} else {
// roughly for data url (a little bit tricky)
return originalSource.size() * 1.5;
if (!originalSource) {
return 0;
}
// roughly for data url
// Example: m.exports="data:image/png;base64,ag82/f+2=="
// 4/3 = base64 encoding
// 34 = ~ data url header + footer + rounding
return originalSource.size() * 1.34 + 36;
} else {
// it's only estimated so this number is probably fine
// Example: m.exports=r.p+"0123456789012345678901.ext"
return 42;
}
}
}
/**
* @param {Hash} hash hash that will be modified
* @param {UpdateHashContext} updateHashContext context for updating hash
*/
updateHash(hash, { module }) {
hash.update(module.buildInfo.dataUrl ? "data-url" : "resource");
}
}
module.exports = AssetGenerator;

View File

@ -6,13 +6,13 @@
"use strict";
const validateOptions = require("schema-utils");
const schema = require("../../schemas/plugins/AssetModulesPlugin.json");
const generatorSchema = require("../../schemas/plugins/AssetModulesPluginGenerator.json");
const parserSchema = require("../../schemas/plugins/AssetModulesPluginParser.json");
const { compareModulesByIdentifier } = require("../util/comparators");
const AssetGenerator = require("./AssetGenerator");
const AssetParser = require("./AssetParser");
/** @typedef {import("webpack-sources").Source} Source */
/** @typedef {import("../../declarations/plugins/AssetModulesPlugin").AssetModulesPluginOptions} AssetModulesPluginOptions */
/** @typedef {import("../Chunk")} Chunk */
/** @typedef {import("../Compiler")} Compiler */
/** @typedef {import("../Module")} Module */
@ -20,48 +20,6 @@ const AssetParser = require("./AssetParser");
const type = "asset";
const plugin = "AssetModulesPlugin";
/**
* @type {Map<string|Buffer, string|null>}
*/
const dataUrlFnCache = new Map();
/**
* @param {AssetModulesPluginOptions} options the options to the encoder
* @returns {AssetModulesPluginOptions} normalized options
*/
const prepareOptions = (options = {}) => {
const dataUrl = options.dataUrl || {};
if (options.dataUrl === false) {
return {
dataUrl: false
};
}
if (typeof dataUrl === "function") {
return {
dataUrl: (source, resourcePath) => {
if (dataUrlFnCache.has(source)) {
return dataUrlFnCache.get(source);
}
const encoded = dataUrl.call(null, source, resourcePath);
dataUrlFnCache.set(source, encoded);
return encoded;
}
};
}
return {
dataUrl: {
encoding: "base64",
maxSize: 8192,
...dataUrl
}
};
};
class AssetModulesPlugin {
/**
* @param {Compiler} compiler webpack compiler
@ -71,21 +29,60 @@ class AssetModulesPlugin {
compiler.hooks.compilation.tap(
plugin,
(compilation, { normalModuleFactory }) => {
normalModuleFactory.hooks.createParser.for(type).tap(plugin, () => {
return new AssetParser();
});
normalModuleFactory.hooks.createGenerator
.for(type)
.tap(plugin, generatorOptions => {
validateOptions(schema, generatorOptions, {
name: "Asset Modules Plugin"
normalModuleFactory.hooks.createParser
.for("asset")
.tap(plugin, parserOptions => {
validateOptions(parserSchema, parserOptions, {
name: "Asset Modules Plugin",
baseDataPath: "parser"
});
return new AssetGenerator(
compilation,
prepareOptions(generatorOptions)
);
let dataUrlCondition = parserOptions.dataUrlCondition;
if (!dataUrlCondition || typeof dataUrlCondition === "object") {
dataUrlCondition = {
maxSize: 8096,
...dataUrlCondition
};
}
return new AssetParser(dataUrlCondition);
});
normalModuleFactory.hooks.createParser
.for("asset/inline")
.tap(plugin, parserOptions => {
return new AssetParser(true);
});
normalModuleFactory.hooks.createParser
.for("asset/resource")
.tap(plugin, parserOptions => {
return new AssetParser(false);
});
for (const type of ["asset", "asset/inline"]) {
normalModuleFactory.hooks.createGenerator
.for(type)
.tap(plugin, generatorOptions => {
validateOptions(generatorSchema, generatorOptions, {
name: "Asset Modules Plugin",
baseDataPath: "generator"
});
let dataUrl = generatorOptions.dataUrl;
if (!dataUrl || typeof dataUrl === "object") {
dataUrl = {
encoding: "base64",
mimetype: undefined,
...dataUrl
};
}
return new AssetGenerator(compilation, dataUrl);
});
}
normalModuleFactory.hooks.createGenerator
.for("asset/resource")
.tap(plugin, () => {
return new AssetGenerator(compilation);
});
compilation.hooks.renderManifest.tap(plugin, (result, options) => {

View File

@ -5,11 +5,37 @@
"use strict";
/** @typedef {import("../../declarations/plugins/AssetModulesPluginParser").AssetModulesPluginParserOptions} AssetModulesPluginParserOptions */
class AssetParser {
/**
* @param {AssetModulesPluginParserOptions["dataUrlCondition"] | boolean} dataUrlCondition condition for inlining as DataUrl
*/
constructor(dataUrlCondition) {
this.dataUrlCondition = dataUrlCondition;
}
parse(source, state) {
state.module.buildInfo.strict = true;
state.module.buildMeta.exportsType = "default";
if (typeof this.dataUrlCondition === "function") {
state.module.buildInfo.dataUrl = this.dataUrlCondition(source, {
filename: state.module.nameForCondition(),
module: state.module
});
} else if (typeof this.dataUrlCondition === "boolean") {
state.module.buildInfo.dataUrl = this.dataUrlCondition;
} else if (
this.dataUrlCondition &&
typeof this.dataUrlCondition === "object"
) {
state.module.buildInfo.dataUrl =
Buffer.byteLength(source) <= this.dataUrlCondition.maxSize;
} else {
throw new Error("Unexpected dataUrlCondition type");
}
return state;
}
}

View File

@ -1,46 +0,0 @@
{
"definitions": {
"DataUrlFn": {
"description": "Function that executes for module source abd resource and should return new module source",
"instanceof": "Function",
"tsType": "((source: string|Buffer, resourcePath: string) => string|null)"
},
"DataUrlOptions": {
"type": "object",
"additionalProperties": false,
"properties": {
"encoding": {
"description": "Module output encoding",
"enum": [false, "base64"]
},
"maxSize": {
"description": "Maximum size of files that should be inline as modules. Default: 8kb",
"type": "number"
},
"mimetype": {
"description": "Module output mimetype (getting from file ext by default)",
"type": "string"
}
}
}
},
"title": "AssetModulesPluginOptions",
"type": "object",
"additionalProperties": false,
"properties": {
"dataUrl": {
"description": "The options for data url generator",
"oneOf": [
{
"enum": [false]
},
{
"$ref": "#/definitions/DataUrlOptions"
},
{
"$ref": "#/definitions/DataUrlFn"
}
]
}
}
}

View File

@ -0,0 +1,39 @@
{
"definitions": {
"DataUrlFunction": {
"description": "Function that executes for module and should return an DataUrl string",
"instanceof": "Function",
"tsType": "((source: string | Buffer, context: { filename: string, module: import('../../lib/Module') }) => string)"
},
"DataUrlOptions": {
"type": "object",
"additionalProperties": false,
"properties": {
"encoding": {
"description": "Asset encoding (defaults to base64)",
"enum": [false, "base64"]
},
"mimetype": {
"description": "Asset mimetype (getting from file extension by default)",
"type": "string"
}
}
}
},
"title": "AssetModulesPluginGeneratorOptions",
"type": "object",
"additionalProperties": false,
"properties": {
"dataUrl": {
"description": "The options for data url generator",
"oneOf": [
{
"$ref": "#/definitions/DataUrlOptions"
},
{
"$ref": "#/definitions/DataUrlFunction"
}
]
}
}
}

View File

@ -0,0 +1,35 @@
{
"definitions": {
"DataUrlFunction": {
"description": "Function that executes for module and should return whenever asset should be inlined as DataUrl",
"instanceof": "Function",
"tsType": "((source: string | Buffer, context: { filename: string, module: import('../../lib/Module') }) => boolean)"
},
"DataUrlOptions": {
"type": "object",
"additionalProperties": false,
"properties": {
"maxSize": {
"description": "Maximum size of asset that should be inline as modules. Default: 8kb",
"type": "number"
}
}
}
},
"title": "AssetModulesPluginParserOptions",
"type": "object",
"additionalProperties": false,
"properties": {
"dataUrlCondition": {
"description": "The condition for inlining the asset as DataUrl",
"oneOf": [
{
"$ref": "#/definitions/DataUrlOptions"
},
{
"$ref": "#/definitions/DataUrlFunction"
}
]
}
}
}

View File

@ -131,17 +131,18 @@ chunk b4a95c5544295741de67.js 899 bytes [rendered]
`;
exports[`StatsTestCases should print correct stats for asset 1`] = `
"Hash: 2f8b01594b17eabf4be0
"Hash: f6c1cb50597248a12805
Time: Xms
Built at: 1970-04-20 12:42:42
Asset Size
6e537cb1f9c5a99c2f2e.png 14.6 KiB [emitted] [immutable] [name: (main)]
bundle.js 10.7 KiB [emitted] [name: main]
Entrypoint main = bundle.js (6e537cb1f9c5a99c2f2e.png)
Asset Size
312082ab35d179f7618f.svg 656 bytes [emitted] [immutable] [name: (main)]
b1586e02d344c0a2b83d.jpg 5.89 KiB [emitted] [immutable] [name: (main)]
bundle.js 10.7 KiB [emitted] [name: main]
Entrypoint main = bundle.js (312082ab35d179f7618f.svg b1586e02d344c0a2b83d.jpg)
./index.js 111 bytes [built]
./images/file.png 42 bytes (javascript) 14.6 KiB (asset) [built]
./images/file.svg 984 bytes [built]
./images/file.jpg 8.83 KiB [built]
./images/file.png 42 bytes [built]
./images/file.svg 915 bytes (javascript) 656 bytes (asset) [built]
./images/file.jpg 7.92 KiB (javascript) 5.89 KiB (asset) [built]
+ 1 hidden module"
`;

View File

@ -15,10 +15,7 @@ module.exports = {
rules: [
{
test: /\.(png|svg)$/,
type: "asset",
generator: {
dataUrl: false
}
type: "asset/resource"
}
]
},

View File

@ -0,0 +1,9 @@
import png from "../_images/file.png";
import svg from "../_images/file.svg";
import jpg from "../_images/file.jpg";
it("should generate various asset types by a custom encoder", () => {
expect(png).toMatch(/^data:image\/png;base64,[0-9a-zA-Z+/]+=*$/);
expect(jpg).toMatch(/^[\da-f]{20}\.jpg$/);
expect(svg).toMatch(/^[\da-f]{20}\.svg$/);
});

View File

@ -0,0 +1,50 @@
const path = require("path");
const NormalModule = require("../../../../lib/NormalModule");
module.exports = {
mode: "development",
module: {
rules: [
{
test: /\.png$/,
type: "asset",
parser: {
dataUrlCondition: (source, { filename, module }) => {
expect(source).toBeInstanceOf(Buffer);
expect(filename).toBe(
path.resolve(__dirname, "../_images/file.png")
);
expect(module).toBeInstanceOf(NormalModule);
return true;
}
}
},
{
test: /\.jpg$/,
type: "asset",
parser: {
dataUrlCondition: (source, { filename, module }) => {
expect(source).toBeInstanceOf(Buffer);
expect(filename).toBe(
path.resolve(__dirname, "../_images/file.jpg")
);
expect(module).toBeInstanceOf(NormalModule);
return false;
}
}
},
{
test: /\.svg$/,
type: "asset",
parser: {
dataUrlCondition: {
maxSize: 0
}
}
}
]
},
experiments: {
asset: true
}
};

View File

@ -3,7 +3,7 @@ import svg from "../_images/file.svg";
import jpg from "../_images/file.jpg";
it("should generate various asset types by a custom encoder", () => {
expect(png).toEqual("7fd64cadadf9a0a1b0c1.png");
expect(jpg).toEqual("5aab95c98ee94873c7a2.jpg");
expect(svg).toEqual("data:image/svg+xml;base64,custom-content");
expect(png).toMatch(/^data:mimetype\/png;base64,[0-9a-zA-Z+/]+=*$/);
expect(jpg).toEqual("data:image/jpg;base64,custom-content");
expect(svg).toMatch(/^data:image\/svg\+xml,/);
});

View File

@ -3,18 +3,29 @@ module.exports = {
module: {
rules: [
{
test: /\.(png|jpg)$/,
type: "asset",
test: /\.png$/,
type: "asset/inline",
generator: {
dataUrl: false
dataUrl: {
mimetype: "mimetype/png"
}
}
},
{
test: /\.jpg$/,
type: "asset/inline",
generator: {
dataUrl() {
return "data:image/jpg;base64,custom-content";
}
}
},
{
test: /\.svg$/,
type: "asset",
generator: {
dataUrl() {
return "data:image/svg+xml;base64,custom-content";
dataUrl: {
encoding: false
}
}
}

View File

@ -4,18 +4,13 @@ module.exports = {
rules: [
{
test: /\.(png|svg)$/,
type: "asset",
generator: {
dataUrl: {
maxSize: Infinity
}
}
type: "asset/inline"
},
{
test: /\.jpg$/,
type: "asset",
generator: {
dataUrl: {
parser: {
dataUrlCondition: {
maxSize: Infinity
}
}

View File

@ -3,7 +3,7 @@ import svg from "../_images/file.svg";
import jpg from "../_images/file.jpg";
it("should output various asset types", () => {
expect(png).toEqual("7fd64cadadf9a0a1b0c1.png");
expect(svg).toEqual("642897943e4a7da2a25e.svg");
expect(jpg).toEqual("5aab95c98ee94873c7a2.jpg");
expect(png).toMatch(/^[\da-f]{20}\.png$/);
expect(svg).toMatch(/^[\da-f]{20}\.svg$/);
expect(jpg).toMatch(/^[\da-f]{20}\.jpg$/);
});

View File

@ -4,17 +4,11 @@ module.exports = {
rules: [
{
test: /\.(png|svg)$/,
type: "asset",
generator: {
dataUrl: false
}
type: "asset/resource"
},
{
test: /\.jpg$/,
type: "asset",
generator: {
dataUrl: false
}
type: "asset/resource"
}
]
},