219 lines
10 KiB
Plaintext
219 lines
10 KiB
Plaintext
import * as path from "path";
|
|
import { webpack, ModuleFilenameHelpers, sources } from "next/dist/compiled/webpack/webpack";
|
|
import pLimit from "next/dist/compiled/p-limit";
|
|
import { Worker } from "next/dist/compiled/jest-worker";
|
|
import { spans } from "../../profiling-plugin";
|
|
function getEcmaVersion(environment) {
|
|
// ES 6th
|
|
if (environment.arrowFunction || environment.const || environment.destructuring || environment.forOf || environment.module) {
|
|
return 2015;
|
|
}
|
|
// ES 11th
|
|
if (environment.bigIntLiteral || environment.dynamicImport) {
|
|
return 2020;
|
|
}
|
|
return 5;
|
|
}
|
|
function buildError(error, file) {
|
|
if (error.line) {
|
|
return new Error(`${file} from Terser\n${error.message} [${file}:${error.line},${error.col}]${error.stack ? `\n${error.stack.split("\n").slice(1).join("\n")}` : ""}`);
|
|
}
|
|
if (error.stack) {
|
|
return new Error(`${file} from Terser\n${error.message}\n${error.stack}`);
|
|
}
|
|
return new Error(`${file} from Terser\n${error.message}`);
|
|
}
|
|
const debugMinify = process.env.NEXT_DEBUG_MINIFY;
|
|
export class TerserPlugin {
|
|
constructor(options = {}){
|
|
const { terserOptions = {}, parallel, swcMinify } = options;
|
|
this.options = {
|
|
swcMinify,
|
|
parallel,
|
|
terserOptions
|
|
};
|
|
}
|
|
async optimize(compiler, compilation, assets, optimizeOptions, cache, { SourceMapSource, RawSource }) {
|
|
const compilationSpan = spans.get(compilation) || spans.get(compiler);
|
|
const terserSpan = compilationSpan.traceChild("terser-webpack-plugin-optimize");
|
|
terserSpan.setAttribute("compilationName", compilation.name);
|
|
terserSpan.setAttribute("swcMinify", this.options.swcMinify);
|
|
return terserSpan.traceAsyncFn(async ()=>{
|
|
let numberOfAssetsForMinify = 0;
|
|
const assetsList = Object.keys(assets);
|
|
const assetsForMinify = await Promise.all(assetsList.filter((name)=>{
|
|
if (!ModuleFilenameHelpers.matchObject.bind(// eslint-disable-next-line no-undefined
|
|
undefined, {
|
|
test: /\.[cm]?js(\?.*)?$/i
|
|
})(name)) {
|
|
return false;
|
|
}
|
|
const res = compilation.getAsset(name);
|
|
if (!res) {
|
|
console.log(name);
|
|
return false;
|
|
}
|
|
// don't minify _middleware as it can break in some cases
|
|
// and doesn't provide too much of a benefit as it's server-side
|
|
if (name.match(/(edge-runtime-webpack\.js|edge-chunks|middleware\.js$)/)) {
|
|
return false;
|
|
}
|
|
const { info } = res;
|
|
// Skip double minimize assets from child compilation
|
|
if (info.minimized) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}).map(async (name)=>{
|
|
const { info, source } = compilation.getAsset(name);
|
|
const eTag = cache.getLazyHashedEtag(source);
|
|
const output = await cache.getPromise(name, eTag);
|
|
if (!output) {
|
|
numberOfAssetsForMinify += 1;
|
|
}
|
|
if (debugMinify && debugMinify === "1") {
|
|
console.dir({
|
|
name,
|
|
source: source.source().toString()
|
|
}, {
|
|
breakLength: Infinity,
|
|
maxStringLength: Infinity
|
|
});
|
|
}
|
|
return {
|
|
name,
|
|
info,
|
|
inputSource: source,
|
|
output,
|
|
eTag
|
|
};
|
|
}));
|
|
const numberOfWorkers = Math.min(numberOfAssetsForMinify, optimizeOptions.availableNumberOfCores);
|
|
let initializedWorker;
|
|
// eslint-disable-next-line consistent-return
|
|
const getWorker = ()=>{
|
|
if (this.options.swcMinify) {
|
|
return {
|
|
minify: async (options)=>{
|
|
const result = await require("../../../../swc").minify(options.input, {
|
|
...options.inputSourceMap ? {
|
|
sourceMap: {
|
|
content: JSON.stringify(options.inputSourceMap)
|
|
}
|
|
} : {},
|
|
compress: true,
|
|
mangle: true
|
|
});
|
|
return result;
|
|
}
|
|
};
|
|
}
|
|
if (initializedWorker) {
|
|
return initializedWorker;
|
|
}
|
|
initializedWorker = new Worker(path.join(__dirname, "./minify.js"), {
|
|
numWorkers: numberOfWorkers,
|
|
enableWorkerThreads: true
|
|
});
|
|
initializedWorker.getStdout().pipe(process.stdout);
|
|
initializedWorker.getStderr().pipe(process.stderr);
|
|
return initializedWorker;
|
|
};
|
|
const limit = pLimit(// When using the SWC minifier the limit will be handled by Node.js
|
|
this.options.swcMinify ? Infinity : numberOfAssetsForMinify > 0 ? numberOfWorkers : Infinity);
|
|
const scheduledTasks = [];
|
|
for (const asset of assetsForMinify){
|
|
scheduledTasks.push(limit(async ()=>{
|
|
const { name, inputSource, info, eTag } = asset;
|
|
let { output } = asset;
|
|
const minifySpan = terserSpan.traceChild("minify-js");
|
|
minifySpan.setAttribute("name", name);
|
|
minifySpan.setAttribute("cache", typeof output === "undefined" ? "MISS" : "HIT");
|
|
return minifySpan.traceAsyncFn(async ()=>{
|
|
if (!output) {
|
|
const { source: sourceFromInputSource, map: inputSourceMap } = inputSource.sourceAndMap();
|
|
const input = Buffer.isBuffer(sourceFromInputSource) ? sourceFromInputSource.toString() : sourceFromInputSource;
|
|
const options = {
|
|
name,
|
|
input,
|
|
inputSourceMap,
|
|
terserOptions: {
|
|
...this.options.terserOptions
|
|
}
|
|
};
|
|
if (typeof options.terserOptions.module === "undefined") {
|
|
if (typeof info.javascriptModule !== "undefined") {
|
|
options.terserOptions.module = info.javascriptModule;
|
|
} else if (/\.mjs(\?.*)?$/i.test(name)) {
|
|
options.terserOptions.module = true;
|
|
} else if (/\.cjs(\?.*)?$/i.test(name)) {
|
|
options.terserOptions.module = false;
|
|
}
|
|
}
|
|
try {
|
|
output = await getWorker().minify(options);
|
|
} catch (error) {
|
|
compilation.errors.push(buildError(error, name));
|
|
return;
|
|
}
|
|
if (output.map) {
|
|
output.source = new SourceMapSource(output.code, name, output.map, input, inputSourceMap, true);
|
|
} else {
|
|
output.source = new RawSource(output.code);
|
|
}
|
|
await cache.storePromise(name, eTag, {
|
|
source: output.source
|
|
});
|
|
}
|
|
const newInfo = {
|
|
minimized: true
|
|
};
|
|
const { source } = output;
|
|
compilation.updateAsset(name, source, newInfo);
|
|
});
|
|
}));
|
|
}
|
|
await Promise.all(scheduledTasks);
|
|
if (initializedWorker) {
|
|
await initializedWorker.end();
|
|
}
|
|
});
|
|
}
|
|
apply(compiler) {
|
|
var _compiler_webpack;
|
|
const { SourceMapSource, RawSource } = (compiler == null ? void 0 : (_compiler_webpack = compiler.webpack) == null ? void 0 : _compiler_webpack.sources) || sources;
|
|
const { output } = compiler.options;
|
|
if (typeof this.options.terserOptions.ecma === "undefined") {
|
|
this.options.terserOptions.ecma = getEcmaVersion(output.environment || {});
|
|
}
|
|
const pluginName = this.constructor.name;
|
|
const availableNumberOfCores = this.options.parallel;
|
|
compiler.hooks.thisCompilation.tap(pluginName, (compilation)=>{
|
|
const cache = compilation.getCache("TerserWebpackPlugin");
|
|
const handleHashForChunk = (hash, _chunk)=>{
|
|
// increment 'c' to invalidate cache
|
|
hash.update("c");
|
|
};
|
|
const JSModulesHooks = webpack.javascript.JavascriptModulesPlugin.getCompilationHooks(compilation);
|
|
JSModulesHooks.chunkHash.tap(pluginName, (chunk, hash)=>{
|
|
if (!chunk.hasRuntime()) return;
|
|
return handleHashForChunk(hash, chunk);
|
|
});
|
|
compilation.hooks.processAssets.tapPromise({
|
|
name: pluginName,
|
|
stage: webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE
|
|
}, (assets)=>this.optimize(compiler, compilation, assets, {
|
|
availableNumberOfCores
|
|
}, cache, {
|
|
SourceMapSource,
|
|
RawSource
|
|
}));
|
|
compilation.hooks.statsPrinter.tap(pluginName, (stats)=>{
|
|
stats.hooks.print.for("asset.info.minimized").tap("terser-webpack-plugin", (minimized, { green, formatFlag })=>// eslint-disable-next-line no-undefined
|
|
minimized ? green(formatFlag("minimized")) : undefined);
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
//# sourceMappingURL=index.js.map |