From 7d6e4822b798ee329278e20ab2349d66de16ea71 Mon Sep 17 00:00:00 2001 From: Hiroki Osame Date: Sat, 19 Aug 2023 22:20:45 +0900 Subject: [PATCH] fix: prevent webpack-merge from cloning --- package.json | 1 + pnpm-lock.yaml | 9 +- src/plugin.ts | 232 +++++++++++++++++++++--------------------- tests/specs/plugin.ts | 20 ++++ 4 files changed, 145 insertions(+), 117 deletions(-) diff --git a/package.json b/package.json index 651843e..30a3a3f 100644 --- a/package.json +++ b/package.json @@ -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" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0715764..e88e113 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -79,6 +79,9 @@ devDependencies: webpack-cli: specifier: ^4.10.0 version: 4.10.0(webpack@4.46.0) + webpack-merge: + specifier: ^5.9.0 + version: 5.9.0 webpack-test-utils: specifier: ^2.1.0 version: 2.1.0(webpack@4.46.0) @@ -6761,11 +6764,11 @@ packages: interpret: 2.2.0 rechoir: 0.7.1 webpack: 4.46.0(webpack-cli@4.10.0) - webpack-merge: 5.8.0 + webpack-merge: 5.9.0 dev: true - /webpack-merge@5.8.0: - resolution: {integrity: sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==} + /webpack-merge@5.9.0: + resolution: {integrity: sha512-6NbRQw4+Sy50vYNTw7EyOn41OZItPiXB8GNv3INSoe3PSFaHJEz3SHTrYVaRm2LilNGnFUzh0FAwqPEmU/CwDg==} engines: {node: '>=10.0.0'} dependencies: clone-deep: 4.0.1 diff --git a/src/plugin.ts b/src/plugin.ts index 698dca8..f992d42 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -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) { + 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).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).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; + } + }); + } } diff --git a/tests/specs/plugin.ts b/tests/specs/plugin.ts index 98c4b16..96dda7c 100644 --- a/tests/specs/plugin.ts +++ b/tests/specs/plugin.ts @@ -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, @@ -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()); + }); }); });