From 53b4562f50610a25322c1262ad14bb5bb91c55fa Mon Sep 17 00:00:00 2001 From: Mirko Budszuhn Date: Wed, 17 Dec 2025 16:02:32 +0100 Subject: [PATCH 1/5] test(print-ready-pdfs): add Webpack 5 compatibility test Add test script to verify plugin works with Webpack 5 bundler (Angular). Currently fails due to Node.js core module imports (module, path). Relates to: https://github.com/imgly/ubq/issues/11471 --- .gitignore | 5 +- .../plugin-print-ready-pdfs-web/package.json | 3 +- .../test/webpack5-angular-test.sh | 78 +++++++++++++++++++ 3 files changed, 84 insertions(+), 2 deletions(-) create mode 100755 packages/plugin-print-ready-pdfs-web/test/webpack5-angular-test.sh diff --git a/.gitignore b/.gitignore index 9f342c46..b7965c83 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,7 @@ yarn-error.log .turbo _ci_* -.pnpm-store \ No newline at end of file +.pnpm-store + +# Test artifacts +packages/*/test/webpack5-angular-project \ No newline at end of file diff --git a/packages/plugin-print-ready-pdfs-web/package.json b/packages/plugin-print-ready-pdfs-web/package.json index 8dc1f0b1..6adfcc6e 100644 --- a/packages/plugin-print-ready-pdfs-web/package.json +++ b/packages/plugin-print-ready-pdfs-web/package.json @@ -67,7 +67,8 @@ "test:content": "pnpm run build && vitest run content-preservation", "test:silent": "pnpm run build && vitest run silent-conversion", "test:all": "pnpm run test:browser && pnpm run test:integration", - "test:visual": "pnpm run build && node test/visual-test.mjs" + "test:visual": "pnpm run build && node test/visual-test.mjs", + "test:webpack5": "bash test/webpack5-angular-test.sh" }, "devDependencies": { "@cesdk/cesdk-js": "~1.61.0", diff --git a/packages/plugin-print-ready-pdfs-web/test/webpack5-angular-test.sh b/packages/plugin-print-ready-pdfs-web/test/webpack5-angular-test.sh new file mode 100755 index 00000000..f64c877f --- /dev/null +++ b/packages/plugin-print-ready-pdfs-web/test/webpack5-angular-test.sh @@ -0,0 +1,78 @@ +#!/bin/bash +# +# Test: Verify @imgly/plugin-print-ready-pdfs-web works with Webpack 5 +# Issue: https://github.com/imgly/ubq/issues/11471 +# +# Exit 0 = PASS (plugin is Webpack 5 compatible) +# Exit 1 = FAIL (plugin has Webpack 5 compatibility issues) +# + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PACKAGE_DIR="$(dirname "$SCRIPT_DIR")" +TEST_PROJECT_DIR="${SCRIPT_DIR}/webpack5-angular-project" + +echo "Testing Webpack 5 compatibility..." + +# Build plugin if needed +if [ ! -f "${PACKAGE_DIR}/dist/index.mjs" ]; then + echo "Building plugin..." + cd "$PACKAGE_DIR" + pnpm run build +fi + +# Create test project if it doesn't exist +if [ ! -d "$TEST_PROJECT_DIR" ]; then + mkdir -p "$TEST_PROJECT_DIR" + cd "$TEST_PROJECT_DIR" + + cat > package.json << 'EOF' +{ + "name": "webpack5-test", + "scripts": { "build": "webpack --mode production" }, + "devDependencies": { + "webpack": "^5.90.0", + "webpack-cli": "^5.1.4", + "typescript": "^5.3.0", + "ts-loader": "^9.5.0" + } +} +EOF + + cat > tsconfig.json << 'EOF' +{ "compilerOptions": { "target": "ES2020", "module": "ESNext", "moduleResolution": "bundler", "strict": false, "skipLibCheck": true, "noImplicitAny": false } } +EOF + + cat > webpack.config.js << 'EOF' +const path = require('path'); +module.exports = { + entry: './src/index.ts', + output: { path: path.resolve(__dirname, 'dist'), filename: 'bundle.js' }, + resolve: { extensions: ['.ts', '.js', '.mjs'] }, + module: { rules: [{ test: /\.ts$/, use: 'ts-loader', exclude: /node_modules/ }] } +}; +EOF + + mkdir -p src + cat > src/index.ts << 'EOF' +// @ts-ignore +import { convertToPDFX3 } from '@imgly/plugin-print-ready-pdfs-web'; +console.log('Plugin loaded:', typeof convertToPDFX3); +EOF + + npm install --silent +fi + +cd "$TEST_PROJECT_DIR" + +# Link the local plugin +mkdir -p "node_modules/@imgly/plugin-print-ready-pdfs-web" +cp -r "${PACKAGE_DIR}/dist/"* "node_modules/@imgly/plugin-print-ready-pdfs-web/" +cp "${PACKAGE_DIR}/package.json" "node_modules/@imgly/plugin-print-ready-pdfs-web/" + +# Run webpack build - should succeed if plugin is Webpack 5 compatible +echo "Running Webpack 5 build..." +npm run build --silent + +echo "PASS: Plugin is Webpack 5 compatible" From c41bf8ecd64e8ddaf2510817829c9e318b761171 Mon Sep 17 00:00:00 2001 From: Mirko Budszuhn Date: Wed, 17 Dec 2025 16:17:18 +0100 Subject: [PATCH 2/5] fix(print-ready-pdfs): make plugin compatible with Webpack 5 Webpack 5 removed automatic Node.js core module polyfills. This caused build failures when bundling the plugin because it statically analyzed imports of 'module', 'path', 'fs', and 'url' even though they were only used in Node.js runtime code paths. Changes: - Use indirect dynamic import via `new Function()` to prevent Webpack from statically analyzing Node.js module imports in production builds - Use direct imports in test environments (vitest) where new Function doesn't have access to the module system's dynamic import callback - Post-process gs.js during build to add `webpackIgnore` magic comments to Emscripten-generated Node.js imports Fixes: https://github.com/imgly/ubq/issues/11471 --- .../esbuild/config.mjs | 38 +++++++++++++++++-- .../plugin-print-ready-pdfs-web/src/pdfx.ts | 20 ++++++++-- .../src/wasm/ghostscript-module.ts | 23 +++++++++-- 3 files changed, 71 insertions(+), 10 deletions(-) diff --git a/packages/plugin-print-ready-pdfs-web/esbuild/config.mjs b/packages/plugin-print-ready-pdfs-web/esbuild/config.mjs index dec17a4b..5087936c 100644 --- a/packages/plugin-print-ready-pdfs-web/esbuild/config.mjs +++ b/packages/plugin-print-ready-pdfs-web/esbuild/config.mjs @@ -1,5 +1,5 @@ import chalk from 'chalk'; -import { readFile, copyFile, mkdir } from 'fs/promises'; +import { readFile, copyFile, mkdir, writeFile } from 'fs/promises'; import { existsSync } from 'fs'; import { join, dirname } from 'path'; import { fileURLToPath } from 'url'; @@ -7,6 +7,33 @@ import { fileURLToPath } from 'url'; import baseConfig from '../../../esbuild/config.mjs'; import log from '../../../esbuild/log.mjs'; +/** + * Add webpackIgnore comments to Node.js module imports in gs.js + * This prevents Webpack 5 from trying to resolve these modules in browser builds. + * See: https://github.com/imgly/ubq/issues/11471 + */ +function addWebpackIgnoreComments(content) { + // Transform: await import("module") -> await import(/* webpackIgnore: true */ "module") + // Transform: await import("path") -> await import(/* webpackIgnore: true */ "path") + // Also handle other Node.js modules that might be imported + const nodeModules = ['module', 'path', 'fs', 'url', 'os']; + let transformed = content; + + for (const mod of nodeModules) { + // Match: import("module") or import('module') + const patterns = [ + new RegExp(`import\\(\\s*["']${mod}["']\\s*\\)`, 'g'), + new RegExp(`import\\(\\s*"${mod}"\\s*\\)`, 'g'), + ]; + + for (const pattern of patterns) { + transformed = transformed.replace(pattern, `import(/* webpackIgnore: true */ "${mod}")`); + } + } + + return transformed; +} + const __dirname = dirname(fileURLToPath(import.meta.url)); // Avoid the Experimental Feature warning when using the above. @@ -31,11 +58,14 @@ const copyWasmPlugin = { await copyFile(srcWasm, distWasm); log(chalk.green('✓ Copied gs.wasm to dist/')); - // Copy gs.js file + // Copy and transform gs.js file to add webpackIgnore comments + // This fixes Webpack 5 compatibility (see https://github.com/imgly/ubq/issues/11471) const srcJs = join(__dirname, '../src/wasm/gs.js'); const distJs = join(distDir, 'gs.js'); - await copyFile(srcJs, distJs); - log(chalk.green('✓ Copied gs.js to dist/')); + const gsContent = await readFile(srcJs, 'utf-8'); + const transformedContent = addWebpackIgnoreComments(gsContent); + await writeFile(distJs, transformedContent); + log(chalk.green('✓ Copied and transformed gs.js to dist/ (added webpackIgnore comments)')); // Copy ICC profile files const iccProfiles = [ diff --git a/packages/plugin-print-ready-pdfs-web/src/pdfx.ts b/packages/plugin-print-ready-pdfs-web/src/pdfx.ts index 9eae3e40..56f2394e 100644 --- a/packages/plugin-print-ready-pdfs-web/src/pdfx.ts +++ b/packages/plugin-print-ready-pdfs-web/src/pdfx.ts @@ -145,9 +145,23 @@ async function convertToPDFX3Single( if (isNode) { // Node.js: Load from filesystem using readFileSync - const { readFileSync } = await import('fs'); - const { fileURLToPath } = await import('url'); - const { dirname, join } = await import('path'); + // Use indirect dynamic import to prevent Webpack 5 from statically analyzing these imports + // But use direct imports in test environments (vitest) where new Function doesn't work + // See: https://github.com/imgly/ubq/issues/11471 + const isTestEnv = + process.env.VITEST === 'true' || process.env.NODE_ENV === 'test'; + + // eslint-disable-next-line @typescript-eslint/no-implied-eval, no-new-func + const indirectImport = new Function('s', 'return import(s)') as ( + s: string + ) => Promise; + const dynamicImport = isTestEnv + ? (s: string) => import(s) + : indirectImport; + + const { readFileSync } = await dynamicImport('fs'); + const { fileURLToPath } = await dynamicImport('url'); + const { dirname, join } = await dynamicImport('path'); // Get the directory of the built module const moduleDir = dirname(fileURLToPath(import.meta.url)); diff --git a/packages/plugin-print-ready-pdfs-web/src/wasm/ghostscript-module.ts b/packages/plugin-print-ready-pdfs-web/src/wasm/ghostscript-module.ts index ef488dcc..5d6b6d4d 100644 --- a/packages/plugin-print-ready-pdfs-web/src/wasm/ghostscript-module.ts +++ b/packages/plugin-print-ready-pdfs-web/src/wasm/ghostscript-module.ts @@ -27,8 +27,25 @@ export default async function createGhostscriptModule( if (isNode) { // Node.js: Use require.resolve to find gs.js relative to this module - const { createRequire } = await import('module'); - const { dirname, join } = await import('path'); + // Use indirect dynamic import to prevent Webpack 5 from statically analyzing these imports + // But use direct imports in test environments (vitest) where new Function doesn't work + // See: https://github.com/imgly/ubq/issues/11471 + const isTestEnv = + typeof process !== 'undefined' && + (process.env.VITEST === 'true' || process.env.NODE_ENV === 'test'); + + // Helper for dynamic imports - uses indirect import in production to avoid Webpack static analysis + // eslint-disable-next-line @typescript-eslint/no-implied-eval, no-new-func + const indirectImport = new Function('s', 'return import(s)') as ( + s: string + ) => Promise; + const dynamicImport = isTestEnv ? (s: string) => import(s) : indirectImport; + + const moduleLib = await dynamicImport('module'); + const pathLib = await dynamicImport('path'); + const createRequire = moduleLib.createRequire; + const dirname = pathLib.dirname; + const join = pathLib.join; const requireForESM = createRequire(import.meta.url); @@ -37,7 +54,7 @@ export default async function createGhostscriptModule( const moduleDir = dirname(gsPath); wasmPath = join(moduleDir, 'gs.wasm'); - GhostscriptModule = await import(gsPath); + GhostscriptModule = await dynamicImport(gsPath); } else { // Browser: Use URL-based imports const moduleUrl = new URL('./gs.js', import.meta.url).href; From d0ff8343dd9eedfe4824a0ed3a7c2f1c16b6b827 Mon Sep 17 00:00:00 2001 From: Mirko Budszuhn Date: Thu, 18 Dec 2025 12:51:39 +0100 Subject: [PATCH 3/5] fix(print-ready-pdfs): address PR review feedback - Remove redundant regex pattern in addWebpackIgnoreComments (the first pattern already matches both single and double quotes) - Rename webpack5-angular-test.sh to webpack5-compatibility-test.sh (the test doesn't use Angular) - Fix misleading comment about vitest - the issue is that indirect imports bypass vitest's mocking system, not that new Function doesn't work --- .../plugin-print-ready-pdfs-web/esbuild/config.mjs | 12 +++--------- packages/plugin-print-ready-pdfs-web/src/pdfx.ts | 2 +- .../src/wasm/ghostscript-module.ts | 2 +- ...ngular-test.sh => webpack5-compatibility-test.sh} | 0 4 files changed, 5 insertions(+), 11 deletions(-) rename packages/plugin-print-ready-pdfs-web/test/{webpack5-angular-test.sh => webpack5-compatibility-test.sh} (100%) diff --git a/packages/plugin-print-ready-pdfs-web/esbuild/config.mjs b/packages/plugin-print-ready-pdfs-web/esbuild/config.mjs index 5087936c..3ab9b1c3 100644 --- a/packages/plugin-print-ready-pdfs-web/esbuild/config.mjs +++ b/packages/plugin-print-ready-pdfs-web/esbuild/config.mjs @@ -20,15 +20,9 @@ function addWebpackIgnoreComments(content) { let transformed = content; for (const mod of nodeModules) { - // Match: import("module") or import('module') - const patterns = [ - new RegExp(`import\\(\\s*["']${mod}["']\\s*\\)`, 'g'), - new RegExp(`import\\(\\s*"${mod}"\\s*\\)`, 'g'), - ]; - - for (const pattern of patterns) { - transformed = transformed.replace(pattern, `import(/* webpackIgnore: true */ "${mod}")`); - } + // Match: import("module") or import('module') with optional whitespace + const pattern = new RegExp(`import\\(\\s*["']${mod}["']\\s*\\)`, 'g'); + transformed = transformed.replace(pattern, `import(/* webpackIgnore: true */ "${mod}")`); } return transformed; diff --git a/packages/plugin-print-ready-pdfs-web/src/pdfx.ts b/packages/plugin-print-ready-pdfs-web/src/pdfx.ts index 56f2394e..f33999c2 100644 --- a/packages/plugin-print-ready-pdfs-web/src/pdfx.ts +++ b/packages/plugin-print-ready-pdfs-web/src/pdfx.ts @@ -146,7 +146,7 @@ async function convertToPDFX3Single( if (isNode) { // Node.js: Load from filesystem using readFileSync // Use indirect dynamic import to prevent Webpack 5 from statically analyzing these imports - // But use direct imports in test environments (vitest) where new Function doesn't work + // But use direct imports in test environments (vitest) where indirect imports bypass mocking // See: https://github.com/imgly/ubq/issues/11471 const isTestEnv = process.env.VITEST === 'true' || process.env.NODE_ENV === 'test'; diff --git a/packages/plugin-print-ready-pdfs-web/src/wasm/ghostscript-module.ts b/packages/plugin-print-ready-pdfs-web/src/wasm/ghostscript-module.ts index 5d6b6d4d..60def6cf 100644 --- a/packages/plugin-print-ready-pdfs-web/src/wasm/ghostscript-module.ts +++ b/packages/plugin-print-ready-pdfs-web/src/wasm/ghostscript-module.ts @@ -28,7 +28,7 @@ export default async function createGhostscriptModule( if (isNode) { // Node.js: Use require.resolve to find gs.js relative to this module // Use indirect dynamic import to prevent Webpack 5 from statically analyzing these imports - // But use direct imports in test environments (vitest) where new Function doesn't work + // But use direct imports in test environments (vitest) where indirect imports bypass mocking // See: https://github.com/imgly/ubq/issues/11471 const isTestEnv = typeof process !== 'undefined' && diff --git a/packages/plugin-print-ready-pdfs-web/test/webpack5-angular-test.sh b/packages/plugin-print-ready-pdfs-web/test/webpack5-compatibility-test.sh similarity index 100% rename from packages/plugin-print-ready-pdfs-web/test/webpack5-angular-test.sh rename to packages/plugin-print-ready-pdfs-web/test/webpack5-compatibility-test.sh From 2d95d7723852ae42404cbe7355dd5dcb73fc4173 Mon Sep 17 00:00:00 2001 From: Mirko Budszuhn Date: Thu, 18 Dec 2025 13:22:02 +0100 Subject: [PATCH 4/5] docs(print-ready-pdfs): add comment explaining CSP is not applicable to Node.js Address PR review feedback about potential CSP failures with new Function(). CSP is a browser security mechanism and doesn't apply to Node.js environments where this code path executes. --- packages/plugin-print-ready-pdfs-web/src/pdfx.ts | 2 ++ .../plugin-print-ready-pdfs-web/src/wasm/ghostscript-module.ts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/packages/plugin-print-ready-pdfs-web/src/pdfx.ts b/packages/plugin-print-ready-pdfs-web/src/pdfx.ts index f33999c2..f71a798b 100644 --- a/packages/plugin-print-ready-pdfs-web/src/pdfx.ts +++ b/packages/plugin-print-ready-pdfs-web/src/pdfx.ts @@ -151,6 +151,8 @@ async function convertToPDFX3Single( const isTestEnv = process.env.VITEST === 'true' || process.env.NODE_ENV === 'test'; + // Note: new Function() could fail in CSP-restricted environments, but CSP is a browser + // security mechanism and doesn't apply to Node.js. This code only runs in Node.js (isNode check above). // eslint-disable-next-line @typescript-eslint/no-implied-eval, no-new-func const indirectImport = new Function('s', 'return import(s)') as ( s: string diff --git a/packages/plugin-print-ready-pdfs-web/src/wasm/ghostscript-module.ts b/packages/plugin-print-ready-pdfs-web/src/wasm/ghostscript-module.ts index 60def6cf..f5a61693 100644 --- a/packages/plugin-print-ready-pdfs-web/src/wasm/ghostscript-module.ts +++ b/packages/plugin-print-ready-pdfs-web/src/wasm/ghostscript-module.ts @@ -35,6 +35,8 @@ export default async function createGhostscriptModule( (process.env.VITEST === 'true' || process.env.NODE_ENV === 'test'); // Helper for dynamic imports - uses indirect import in production to avoid Webpack static analysis + // Note: new Function() could fail in CSP-restricted environments, but CSP is a browser + // security mechanism and doesn't apply to Node.js. This code only runs in Node.js (isNode check above). // eslint-disable-next-line @typescript-eslint/no-implied-eval, no-new-func const indirectImport = new Function('s', 'return import(s)') as ( s: string From 7360da2c50ca59e37928d0feb5c19439a6d5c4c4 Mon Sep 17 00:00:00 2001 From: Mirko Budszuhn Date: Thu, 18 Dec 2025 14:58:59 +0100 Subject: [PATCH 5/5] chore(print-ready-pdfs): bump version to 1.1.1 - Add changelog entry for Webpack 5 compatibility fix - Fix test:webpack5 script to reference renamed test file --- packages/plugin-print-ready-pdfs-web/CHANGELOG.md | 6 ++++++ packages/plugin-print-ready-pdfs-web/package.json | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/plugin-print-ready-pdfs-web/CHANGELOG.md b/packages/plugin-print-ready-pdfs-web/CHANGELOG.md index a86ceb96..c8951c5d 100644 --- a/packages/plugin-print-ready-pdfs-web/CHANGELOG.md +++ b/packages/plugin-print-ready-pdfs-web/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.1.1] - 2025-12-18 + +### Fixed + +- Fixed Webpack 5 compatibility issue where Node.js module imports (`module`, `path`, `fs`, `url`) caused build failures in Angular 17+ and other Webpack 5 environments ([#11471](https://github.com/imgly/ubq/issues/11471)) + ## [1.1.0] - 2025-12-03 ### Added diff --git a/packages/plugin-print-ready-pdfs-web/package.json b/packages/plugin-print-ready-pdfs-web/package.json index 6adfcc6e..3c50ff0c 100644 --- a/packages/plugin-print-ready-pdfs-web/package.json +++ b/packages/plugin-print-ready-pdfs-web/package.json @@ -1,6 +1,6 @@ { "name": "@imgly/plugin-print-ready-pdfs-web", - "version": "1.1.0", + "version": "1.1.1", "description": "Print-Ready PDFs plugin for CE.SDK editor - PDF/X conversion and export functionality. Contains AGPL-3.0 licensed Ghostscript WASM.", "keywords": [ "CE.SDK", @@ -68,7 +68,7 @@ "test:silent": "pnpm run build && vitest run silent-conversion", "test:all": "pnpm run test:browser && pnpm run test:integration", "test:visual": "pnpm run build && node test/visual-test.mjs", - "test:webpack5": "bash test/webpack5-angular-test.sh" + "test:webpack5": "bash test/webpack5-compatibility-test.sh" }, "devDependencies": { "@cesdk/cesdk-js": "~1.61.0",