Skip to content

Commit

Permalink
fix: prevent webpack-merge from cloning
Browse files Browse the repository at this point in the history
  • Loading branch information
privatenumber committed Aug 19, 2023
1 parent f4efa60 commit 7d6e482
Show file tree
Hide file tree
Showing 4 changed files with 145 additions and 117 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
"typescript": "^5.1.6",
"webpack": "^4.44.2",
"webpack-cli": "^4.10.0",
"webpack-merge": "^5.9.0",
"webpack-test-utils": "^2.1.0",
"webpack5": "npm:webpack@^5.0.0"
},
Expand Down
9 changes: 6 additions & 3 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

232 changes: 118 additions & 114 deletions src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,131 +112,135 @@ const transformAssets = async (
}));
};

export default function EsbuildPlugin(
{
implementation,
...options
}: EsbuildPluginOptions = {},
) {
if (
implementation
&& typeof implementation.transform !== 'function'
export default class EsbuildPlugin {
options: EsbuildPluginOptions;

constructor(
options: EsbuildPluginOptions = {},
) {
throw new TypeError(
`[${pluginName}] implementation.transform must be an esbuild transform function. Received ${typeof implementation.transform}`,
);
}
const { implementation } = options;
if (
implementation
&& typeof implementation.transform !== 'function'
) {
throw new TypeError(
`[${pluginName}] implementation.transform must be an esbuild transform function. Received ${typeof implementation.transform}`,
);
}

const transform = implementation?.transform ?? defaultEsbuildTransform;
this.options = options;
}

const pluginInstance = {
apply(compiler: Compiler) {
if (!('format' in options)) {
const { target } = compiler.options;
const isWebTarget = (
Array.isArray(target)
? target.includes('web')
: target === 'web'
);
const wontGenerateHelpers = !options.target || (
Array.isArray(options.target)
? (
options.target.length === 1
&& options.target[0] === 'esnext'
)
: options.target === 'esnext'
);
apply(compiler: Compiler) {

Check warning on line 134 in src/plugin.ts

View workflow job for this annotation

GitHub Actions / Test

Method 'apply' has a complexity of 14. Maximum allowed is 10

Check warning on line 134 in src/plugin.ts

View workflow job for this annotation

GitHub Actions / Release

Method 'apply' has a complexity of 14. Maximum allowed is 10
const {
implementation,
...options
} = this.options;
const transform = implementation?.transform ?? defaultEsbuildTransform;

if (!('format' in options)) {
const { target } = compiler.options;
const isWebTarget = (
Array.isArray(target)
? target.includes('web')
: target === 'web'
);
const wontGenerateHelpers = !options.target || (
Array.isArray(options.target)
? (
options.target.length === 1
&& options.target[0] === 'esnext'
)
: options.target === 'esnext'
);

if (isWebTarget && !wontGenerateHelpers) {
options.format = 'iife';
}
if (isWebTarget && !wontGenerateHelpers) {
options.format = 'iife';
}
}

/**
* Enable minification by default if used in the minimizer array
* unless further specified in the options
*/
const usedAsMinimizer = compiler.options.optimization?.minimizer?.includes?.(this);
if (
usedAsMinimizer
&& !(
'minify' in options
|| 'minifyWhitespace' in options
|| 'minifyIdentifiers' in options
|| 'minifySyntax' in options
)
) {
options.minify = compiler.options.optimization?.minimize;
}

compiler.hooks.compilation.tap(pluginName, (compilation) => {
const meta = JSON.stringify({
name: 'esbuild-loader',
version,
options,
});

compilation.hooks.chunkHash.tap(
pluginName,
(_, hash) => hash.update(meta),
);

/**
* Enable minification by default if used in the minimizer array
* unless further specified in the options
* Check if sourcemaps are enabled
* Webpack 4: https://github.com/webpack/webpack/blob/v4.46.0/lib/SourceMapDevToolModuleOptionsPlugin.js#L20
* Webpack 5: https://github.com/webpack/webpack/blob/v5.75.0/lib/SourceMapDevToolModuleOptionsPlugin.js#LL27
*/
const usedAsMinimizer = compiler.options.optimization?.minimizer?.includes?.(pluginInstance);
if (
usedAsMinimizer
&& !(
'minify' in options
|| 'minifyWhitespace' in options
|| 'minifyIdentifiers' in options
|| 'minifySyntax' in options
)
) {
options.minify = compiler.options.optimization?.minimize;
}

compiler.hooks.compilation.tap(pluginName, (compilation) => {
const meta = JSON.stringify({
name: 'esbuild-loader',
version,
options,
});
let useSourceMap = false;
compilation.hooks.finishModules.tap(
pluginName,
(modules) => {
const firstModule = (
Array.isArray(modules)
? modules[0]
: (modules as Set<webpack5.Module>).values().next().value as webpack5.Module
);
useSourceMap = firstModule.useSourceMap;
},
);

compilation.hooks.chunkHash.tap(
pluginName,
(_, hash) => hash.update(meta),
// Webpack 5
if ('processAssets' in compilation.hooks) {
compilation.hooks.processAssets.tapPromise(
{
name: pluginName,
// @ts-expect-error undefined on Function type
stage: compilation.constructor.PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE,
additionalAssets: true,
},
() => transformAssets(options, transform, compilation, useSourceMap),
);

/**
* Check if sourcemaps are enabled
* Webpack 4: https://github.com/webpack/webpack/blob/v4.46.0/lib/SourceMapDevToolModuleOptionsPlugin.js#L20
* Webpack 5: https://github.com/webpack/webpack/blob/v5.75.0/lib/SourceMapDevToolModuleOptionsPlugin.js#LL27
*/
let useSourceMap = false;
compilation.hooks.finishModules.tap(
pluginName,
(modules) => {
const firstModule = (
Array.isArray(modules)
? modules[0]
: (modules as Set<webpack5.Module>).values().next().value as webpack5.Module
compilation.hooks.statsPrinter.tap(pluginName, (statsPrinter) => {
statsPrinter.hooks.print
.for('asset.info.minimized')
.tap(
pluginName,
(
minimized,
{ green, formatFlag },
// @ts-expect-error type incorrectly doesn't accept undefined
) => (
minimized
// @ts-expect-error type incorrectly doesn't accept undefined
? green(formatFlag('minimized'))
: undefined
),
);
useSourceMap = firstModule.useSourceMap;
},
});
} else {
compilation.hooks.optimizeChunkAssets.tapPromise(
pluginName,
() => transformAssets(options, transform, compilation, useSourceMap),
);

// Webpack 5
if ('processAssets' in compilation.hooks) {
compilation.hooks.processAssets.tapPromise(
{
name: pluginName,
// @ts-expect-error undefined on Function type
stage: compilation.constructor.PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE,
additionalAssets: true,
},
() => transformAssets(options, transform, compilation, useSourceMap),
);

compilation.hooks.statsPrinter.tap(pluginName, (statsPrinter) => {
statsPrinter.hooks.print
.for('asset.info.minimized')
.tap(
pluginName,
(
minimized,
{ green, formatFlag },
// @ts-expect-error type incorrectly doesn't accept undefined
) => (
minimized
// @ts-expect-error type incorrectly doesn't accept undefined
? green(formatFlag('minimized'))
: undefined
),
);
});
} else {
compilation.hooks.optimizeChunkAssets.tapPromise(
pluginName,
() => transformAssets(options, transform, compilation, useSourceMap),
);
}
});
},
};

return pluginInstance;
}
});
}
}
20 changes: 20 additions & 0 deletions tests/specs/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { build } from 'webpack-test-utils';
import webpack4 from 'webpack';
import webpack5 from 'webpack5';
import * as esbuild from 'esbuild';
import { merge } from 'webpack-merge';
import {
isWebpack4,
configureEsbuildMinifyPlugin,
Expand Down Expand Up @@ -700,5 +701,24 @@ export default testSuite(({ describe }, webpack: typeof webpack4 | typeof webpac
expect(countIife(code)).toBe(webpackIs4 ? 1 : 2);
});
});

test('supports webpack-merge', async () => {
const built = await build(
fixtures.minification,
(config) => {
configureEsbuildMinifyPlugin(config);
const clonedConfig = merge({}, config);
config.optimization = clonedConfig.optimization;
},
webpack,
);

expect(built.stats.hasWarnings()).toBe(false);
expect(built.stats.hasErrors()).toBe(false);

const exportedFunction = built.require('/dist/');
expect(exportedFunction('hello world')).toBe('hello world');
assertMinified(exportedFunction.toString());
});
});
});

0 comments on commit 7d6e482

Please sign in to comment.