Vite has become the de facto standard for modern frontend development thanks to its fast dev server and first-class ESM support, but its dependency pre-bundling and static-asset pipeline impose specific requirements on libraries that ship WebAssembly. dompdf.js bundles a Rust/WASM core and a worker pipeline, so without the right entries in vite.config.js you can end up in the classic trap: everything works in development, the production build compiles cleanly, and then the deployed app crashes at runtime because the wasm file failed to load or its path no longer resolves. The good news is that the required configuration is small, well-understood, and one-time: optimizeDeps.exclude keeps the library out of pre-bundling, worker.format controls how workers are emitted, and assetsInclude makes sure .wasm files are handled as assets. This guide walks through installation and basic usage first, then presents a complete vite.config.js that covers all three concerns, explains the differences between the dev server and the production build that routinely confuse teams, shows the correct way to create workers with import.meta.url, and closes with a troubleshooting section for the errors you will actually encounter. After reading, you should be able to configure a Vite project once and never think about build issues again. The configuration shown is deliberately minimal so it can be understood line by line, and every entry is explained in terms of the failure it prevents. The guide also covers the worker creation patterns that integrate cleanly with Vite's bundler, the deployment paths and cache behavior that differ between dev and production, and the four troubleshooting questions that resolve the vast majority of real-world issues.
Vite serves dependencies as native ESM during development and bundles them with Rollup for production, and the two mechanisms treat dependencies differently. During pre-bundling, Vite converts dependencies to ESM and rewrites their internal imports; for a library that loads binary resources and spawns workers, that rewriting can break path references that the library computed relative to its own module location. Adding the library to optimizeDeps.exclude tells Vite to leave it as native ESM, which is the standard, documented remedy for WASM-bearing dependencies.
The second critical point is how .wasm files are handled. Vite treats .wasm as a static asset by default, but if the library fetches its wasm at runtime with a path derived from import.meta.url, the built asset path must match the deployed path. Configuring assetsInclude so .wasm files are explicitly recognized as assets, and verifying the emitted references after a build, prevents the runtime wasm 404 that only ever appears in production — the dev server masks it because it serves everything from memory.
The reassuring part is that all of this is a one-time cost. Once the configuration is in place, development, build, and deployment behave consistently, and every team member reuses the same setup. More valuable than the config itself is understanding why each entry exists: pre-bundling, asset inlining, and path resolution are the three mechanisms at play, and knowing them lets you reason about any future build error instead of treating the config file as an incantation to be copied verbatim.
Version coupling deserves a note. When you upgrade dompdf.js, the configuration usually survives unchanged, but if a new major version changes how the library loads its wasm or spawns workers, the changelog will say so — and you should verify against it. Recording the library version alongside the configuration in your project docs turns every future upgrade from a guessing game into a checklist item.
Install the package, then import it where you need it. The basic usage is deliberately simple: create a DomPDF instance, add content with addPage using an HTML string or a DOM element, and trigger the download with save. Because PDF export is a heavyweight operation, wrap it in a dedicated export module rather than scattering calls across components; a single wrapper gives you one place to optimize, instrument, and test, and callers only ever see a small function signature.
Treat options such as page format and margins as configuration parameters of the wrapper, with defaults inside the function and overrides available per call. Business components then care only about exporting this data, not about PDF internals, and the wrapper's interface stays small and predictable. This shape also gives you a natural place to later add worker dispatch, progress callbacks, and error reporting without changing any caller.
If your project uses TypeScript, confirm that type declarations are available so the editor surfaces DomPDF's method signatures. Type-level validation catches parameter mistakes at compile time instead of runtime, which matters for a library whose errors surface as blank PDFs rather than loud crashes. Correct typings also make refactoring safer: the editor updates every call site when the wrapper's interface changes.
A useful mental model is that Vite's pre-bundling is an optimization, not a requirement: dependencies that cannot be safely rewritten — WASM-bearing libraries, workers, and packages with dynamic paths — belong in the exclude list. When in doubt, exclude first and measure the performance impact; the dev-server slowdown from excluding a single library is usually negligible compared with the runtime failures it prevents.
// Install the dependency
// npm install dompdf.js
// export-report.js — wrap the export logic
import { DomPDF } from 'dompdf.js';
export async function exportReport(html, filename = 'report.pdf') {
const pdf = new DomPDF();
pdf.addPage(html, { format: 'A4' });
pdf.save(filename);
}
This configuration covers the three integration concerns for dompdf.js: optimizeDeps.exclude keeps the library out of pre-bundling, worker.format declares the worker output format, and assetsInclude explicitly recognizes wasm files as assets. Each entry maps to a distinct failure mode — missing exclude causes broken internal paths after pre-bundling, a wrong worker format breaks module workers at runtime, and missing assetsInclude risks wasm 404s after the production build.
The reasoning behind each line is worth internalizing. optimizeDeps.exclude prevents Vite from rewriting the library's internal import graph during pre-bundling, which would break worker and wasm references that the library computes relative to its own location. worker.format: 'es' emits workers in ESM format, matching the import syntax used inside module workers; the alternative, 'iife', pairs with classic workers and must match the type you pass when constructing the Worker. assetsInclude ensures wasm files are treated as assets and emitted to the output directory. build.target es2020 aligns the emitted syntax with the browser features the library expects; adjust it to match your supported browsers.
After configuring, run a quick verification: export once in development and once from the production build, and confirm both succeed with identical results. Adding these two checks to your integration acceptance checklist means anyone who later touches the build configuration can confirm they did not break PDF export, at essentially zero ongoing cost.
The wrapper also gives you a natural place to log export attempts, measure duration, and report failures to your monitoring pipeline, so operational visibility comes from one line of instrumentation instead of scattered logging. Keep the wrapper synchronous in contract and asynchronous in implementation, and callers never need to know whether rendering happens on the main thread or in a worker.
// vite.config.js
import { defineConfig } from 'vite';
export default defineConfig({
optimizeDeps: {
exclude: ['dompdf.js'],
},
worker: {
format: 'es',
},
assetsInclude: ['**/*.wasm'],
build: {
target: 'es2020',
},
});
Vite offers two idiomatic ways to create workers. The first — new Worker(new URL('./worker.js', import.meta.url), { type: 'module' }) — is the officially recommended form: Vite recognizes the pattern, bundles the worker as a separate file, and manages its URL. The second uses the ?worker import suffix, which yields a Worker constructor you can instantiate with parameters, handy when the worker needs configuration at creation time. Both forms keep worker code as real modules; neither works if you inline worker source in a string, because the build tool cannot analyze or bundle what it cannot see.
dompdf.js's internal worker pipeline relies on the same path-resolution mechanics, and the configuration above guarantees that both the worker and the wasm file are emitted correctly after bundling. If you wrap your own export worker, keep the worker file inside src so the build tool discovers and processes it; files placed in public bypass bundling entirely, which means no content hashing, no path rewriting, and a predictable source of deployment path bugs.
When a worker fails with an import error at runtime, inspect the emitted worker file in the build output: its format must match the type you pass to the Worker constructor. An 'es' worker paired with type: 'module' works; an 'iife' worker loaded as a module fails, and vice versa. Format mismatches between creation and bundling are the single most common worker runtime error, and the fix is a one-line alignment in either the config or the constructor call.
If your project serves multiple environments from one config — staging, production, CDN-prefixed URLs — factor the environment-specific parts into a function or a small plugin so the dompdf.js entries stay identical across environments. The three integration entries rarely need to change; keeping them constant reduces the chance that an environment-specific tweak accidentally breaks export.
In development, Vite compiles on demand and serves dependencies as native ESM; the dev server provides the wasm file from memory, and path problems are rare. The production build runs through Rollup: assets receive content hashes and are emitted into dist, so any hard-coded resource path in your code is guaranteed to 404 after deployment. The rule that eliminates an entire class of bugs is to reference every resource through relative paths or imports, never through hard-coded absolute strings.
The base option deserves its own checklist entry. If the application is deployed under a sub-path — for example https://example.com/app/ — the base setting in vite.config.js must match, or every absolute asset URL breaks: wasm, workers, fonts, images. Debugging asset-path problems follows one pattern regardless of the tool: open the network panel on the production site, find the 404 request, and trace its reference chain back to the configuration that produced it. Most cases resolve in minutes.
Performance differences between the environments are also worth knowing. Development builds skip compression and tree shaking, so dompdf.js loads heavier and the first export feels slower than production. That is expected, not a regression — never judge production performance from the dev server. Establish your performance baseline against the production build, record it, and compare future measurements against that baseline so optimization efforts target real differences rather than dev-mode artifacts.
Finally, remember that the dev server and the deployed origin may serve different CORS policies. If the library fetches its wasm from a CDN or a second origin in production, confirm the CORS headers on that origin before launch; the dev server's same-origin convenience masks cross-origin failures that appear only after deployment. A one-time verification of the production request headers saves a support ticket later.
Asset paths and caching are two sides of the same deployment story. Vite emits hashed asset filenames, so the browser can cache them aggressively and still receive new versions automatically when content changes. The same logic applies to the wasm file and any worker files the library emits: they should be served with long-lived cache headers and immutable hashed names. Configure your CDN or static host accordingly, and confirm that the wasm file's content type is served correctly.
Cache correctness has a failure mode of its own: stale service workers or proxy caches can keep serving an old wasm file against a new application bundle, producing subtle rendering bugs that are nearly impossible to reproduce locally. When a production-only export failure appears after a deployment, force-refresh verification and a cache-buster URL are the first diagnostic steps; versioned asset names usually resolve the issue permanently.
If your deployment pipeline splits assets across hosts — application on one CDN, wasm on another — verify that the cross-origin requests carry the right headers and that the asset host's cache policy is aligned with the application's release cycle. Document the asset topology in the deployment runbook; when the inevitable incident occurs, the runbook turns a fire drill into a checklist.
Environment differences extend to performance expectations. The first export in a production session pays one-time costs — wasm download, worker startup, module parsing — that make it slower than subsequent exports, so benchmark the warm path, not just the cold one. Browser caching makes repeated exports progressively cheaper: wasm and worker files with content hashes are fetched once and reused, which is exactly why the cache configuration in the deployment section matters. Plan the acceptance test to cover both the first export and the second, and record both numbers in the baseline.
Q: Works in dev, crashes after build? A: Check three things in order: whether optimizeDeps.exclude is present, whether the wasm file was emitted to dist, and whether the worker loads as a module. The overwhelming majority of build-time failures live in these three places, and each maps to a configuration entry in the example above.
Q: wasm 404 in the console? A: Verify assetsInclude and base, then compare the wasm file's actual path inside dist with the URL the browser requested. Use relative references or align the deployment path; check cache headers as well, because a stale cached 404 can persist after the underlying issue is fixed.
Q: The worker file was not bundled? A: Confirm the worker is created with new URL plus import.meta.url, and that the worker file lives inside src. String-based worker paths cannot be analyzed by the build tool and will silently bypass bundling. With these three questions, Vite integration issues are resolved quickly and the configuration, once correct, rarely needs touching again.
Q: Export works locally but fails for some users? A: Suspect environment differences: browser version support for wasm and module workers, network-level blocking of the asset host, or aggressive caching proxies. Check the browser support matrix against build.target, verify the asset host is reachable from the affected regions, and look for cache-related reports. A fallback that renders on the main thread when workers are unavailable keeps the feature alive across all environments.
The four troubleshooting questions are ordered by frequency of occurrence, so working through them in sequence resolves most issues without deeper investigation. For anything remaining, the network panel and the emitted dist directory together contain the answer: compare the requested URL with the emitted file, and the discrepancy names the misconfiguration. Document each resolved issue in the project's troubleshooting notes, because the fifth question a future teammate asks is usually one you have already answered.
下面的按钮用 dompdf.js 在浏览器端实时生成 PDF,无需后端:
这是由 dompdf.js 渲染的示例 PDF 内容。