Skip to content

Commit

Permalink
fix: source map generation for multiple minify functions
Browse files Browse the repository at this point in the history
  • Loading branch information
alexander-akait committed Oct 4, 2021
1 parent c545405 commit b736099
Show file tree
Hide file tree
Showing 13 changed files with 523 additions and 185 deletions.
129 changes: 82 additions & 47 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class CssMinimizerPlugin {
);
}

static buildError(error, name, requestShortener, sourceMap) {
static buildError(error, name, sourceMap, requestShortener) {
let builtError;

if (error.line) {
Expand Down Expand Up @@ -148,7 +148,17 @@ class CssMinimizerPlugin {
return null;
}

return `Css Minimizer Plugin: ${warningMessage} ${locationMessage}`;
const builtWarning = new Error(
`Css Minimizer Plugin: ${warningMessage}${
locationMessage ? ` ${locationMessage}` : ""
}`
);

builtWarning.name = "Warning";
builtWarning.hideStack = true;
builtWarning.file = file;

return builtWarning;
}

static getAvailableNumberOfCores(parallel) {
Expand Down Expand Up @@ -266,8 +276,6 @@ class CssMinimizerPlugin {
if (CssMinimizerPlugin.isSourceMap(map)) {
inputSourceMap = map;
} else {
inputSourceMap = map;

compilation.warnings.push(
new Error(`${name} contains invalid source map`)
);
Expand All @@ -286,76 +294,103 @@ class CssMinimizerPlugin {
minifyOptions: this.options.minimizerOptions,
};

let result;

try {
output = await (getWorker
result = await (getWorker
? getWorker().transform(serialize(options))
: minifyFn(options));
} catch (error) {
const hasSourceMap =
inputSourceMap &&
CssMinimizerPlugin.isSourceMap(inputSourceMap);

compilation.errors.push(
CssMinimizerPlugin.buildError(
error,
name,
compilation.requestShortener,
inputSourceMap &&
CssMinimizerPlugin.isSourceMap(inputSourceMap)
hasSourceMap
? new SourceMapConsumer(inputSourceMap)
: null
: // eslint-disable-next-line no-undefined
undefined,
// eslint-disable-next-line no-undefined
hasSourceMap ? compilation.requestShortener : undefined
)
);

return;
}

if (output.map) {
output.source = new SourceMapSource(
output.code,
name,
output.map,
input,
inputSourceMap,
true
);
} else {
output.source = new RawSource(output.code);
output = { warnings: [], errors: [] };

for (const item of result.outputs) {
if (item.map) {
let originalSource;
let innerSourceMap;

if (output.source) {
({ source: originalSource, map: innerSourceMap } =
output.source.sourceAndMap());
} else {
originalSource = input;
innerSourceMap = inputSourceMap;
}

// TODO need API for merging source maps in `webpack-source`
output.source = new SourceMapSource(
item.code,
name,
item.map,
originalSource,
innerSourceMap,
true
);
} else {
output.source = new RawSource(item.code);
}
}

if (output.warnings && output.warnings.length > 0) {
output.warnings = output.warnings
.map((warning) =>
CssMinimizerPlugin.buildWarning(
warning,
name,
inputSourceMap &&
CssMinimizerPlugin.isSourceMap(inputSourceMap)
? new SourceMapConsumer(inputSourceMap)
: null,
compilation.requestShortener,
this.options.warningsFilter
)
)
.filter(Boolean);
if (result.warnings && result.warnings.length > 0) {
const hasSourceMap =
inputSourceMap &&
CssMinimizerPlugin.isSourceMap(inputSourceMap);

for (const warning of result.warnings) {
const buildWarning = CssMinimizerPlugin.buildWarning(
warning,
name,
hasSourceMap
? new SourceMapConsumer(inputSourceMap)
: // eslint-disable-next-line no-undefined
undefined,
// eslint-disable-next-line no-undefined
hasSourceMap ? compilation.requestShortener : undefined,
this.options.warningsFilter
);

if (buildWarning) {
output.warnings.push(buildWarning);
}
}
}

await cacheItem.storePromise({
source: output.source,
warnings: output.warnings,
errors: output.errors,
});
}

if (output.warnings && output.warnings.length > 0) {
output.warnings.forEach((warning) => {
const Warning = class Warning extends Error {
constructor(message) {
super(message);

this.name = "Warning";
this.hideStack = true;
this.file = name;
}
};
for (const warning of output.warnings) {
compilation.warnings.push(warning);
}
}

compilation.warnings.push(new Warning(warning));
});
if (output.errors && output.errors.length > 0) {
for (const error of output.errors) {
compilation.errors.push(error);
}
}

const newInfo = { minimized: true };
Expand Down
45 changes: 33 additions & 12 deletions src/minify.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,52 @@ const minify = async (options) => {
const minifyFns =
typeof options.minify === "function" ? [options.minify] : options.minify;

const result = {
code: options.input,
map: options.inputSourceMap,
warnings: [],
};
const result = { outputs: [], warnings: [], errors: [] };

let needSourceMap = false;

for (let i = 0; i <= minifyFns.length - 1; i++) {
const minifyFn = minifyFns[i];
const minifyOptions = Array.isArray(options.minifyOptions)
? options.minifyOptions[i]
: options.minifyOptions;
const prevResult =
result.outputs.length > 0
? result.outputs[result.outputs.length - 1]
: { code: options.input, map: options.inputSourceMap };
const { code, map } = prevResult;
// eslint-disable-next-line no-await-in-loop
const minifyResult = await minifyFn(
{ [options.name]: result.code },
result.map,
{ [options.name]: code },
map,
minifyOptions
);

result.code = minifyResult.code;
result.map = minifyResult.map;
result.warnings = result.warnings.concat(minifyResult.warnings || []);
if (!minifyResult.code) {
throw new Error("Minimizer function doesn't return result");
}

if (minifyResult.map) {
needSourceMap = true;
}

if (minifyResult.errors) {
result.errors = result.errors.concat(
minifyResult.errors.map((error) => error.toString())
);
}

if (minifyResult.warnings) {
result.warnings = result.warnings.concat(
minifyResult.warnings.map((warning) => warning.toString())
);
}

result.outputs.push({ code: minifyResult.code, map: minifyResult.map });
}

if (result.warnings.length > 0) {
result.warnings = result.warnings.map((warning) => warning.toString());
if (!needSourceMap) {
result.outputs = [result.outputs[result.outputs.length - 1]];
}

return result;
Expand Down
74 changes: 31 additions & 43 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,34 @@ async function cssnanoMinify(
inputSourceMap,
minimizerOptions = { preset: "default" }
) {
const load = async (module) => {
let exports;

try {
// eslint-disable-next-line import/no-dynamic-require, global-require
exports = require(module);

return exports;
} catch (requireError) {
let importESM;

try {
// eslint-disable-next-line no-new-func
importESM = new Function("id", "return import(id);");
} catch (e) {
importESM = null;
}

if (requireError.code === "ERR_REQUIRE_ESM" && importESM) {
exports = await importESM(module);

return exports.default;
}

throw requireError;
}
};

const [[name, input]] = Object.entries(data);
const postcssOptions = {
to: name,
Expand Down Expand Up @@ -42,10 +70,7 @@ async function cssnanoMinify(
}

if (inputSourceMap) {
postcssOptions.map = {
annotation: false,
prev: inputSourceMap,
};
postcssOptions.map = { annotation: false };
}

// eslint-disable-next-line global-require
Expand All @@ -62,56 +87,19 @@ async function cssnanoMinify(
map: result.map && result.map.toString(),
warnings: result.warnings().map(String),
};

async function load(module) {
let exports;

try {
// eslint-disable-next-line import/no-dynamic-require, global-require
exports = require(module);

return exports;
} catch (requireError) {
let importESM;

try {
// eslint-disable-next-line no-new-func
importESM = new Function("id", "return import(id);");
} catch (e) {
importESM = null;
}

if (requireError.code === "ERR_REQUIRE_ESM" && importESM) {
exports = await importESM(module);

return exports.default;
}

throw requireError;
}
}
}

/* istanbul ignore next */
async function cssoMinify(data, inputSourceMap, minimizerOptions) {
// eslint-disable-next-line global-require,import/no-extraneous-dependencies
const csso = require("csso");
// eslint-disable-next-line global-require
const sourcemap = require("source-map");
const [[filename, input]] = Object.entries(data);
const result = csso.minify(input, {
filename,
sourceMap: inputSourceMap,
sourceMap: Boolean(inputSourceMap),
...minimizerOptions,
});

if (inputSourceMap) {
result.map.applySourceMap(
new sourcemap.SourceMapConsumer(inputSourceMap),
filename
);
}

return {
code: result.css,
map: result.map && result.map.toJSON(),
Expand All @@ -124,7 +112,7 @@ async function cleanCssMinify(data, inputSourceMap, minimizerOptions) {
const CleanCSS = require("clean-css");
const [[name, input]] = Object.entries(data);
const result = await new CleanCSS({
sourceMap: inputSourceMap,
sourceMap: Boolean(inputSourceMap),
...minimizerOptions,
}).minify({ [name]: { styles: input } });

Expand Down
4 changes: 2 additions & 2 deletions test/CssMinimizerPlugin.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,8 @@ describe("CssMinimizerPlugin", () => {
CssMinimizerPlugin.buildError(
otherErrorWithLineAndCol,
"test.css",
new RequestShortener("/example.com/www/js/"),
new SourceMapConsumer(rawSourceMap)
new SourceMapConsumer(rawSourceMap),
new RequestShortener("/example.com/www/js/")
)
).toMatchSnapshot();

Expand Down

0 comments on commit b736099

Please sign in to comment.