diff --git a/README.md b/README.md
index 76d3be8..6a51cff 100644
--- a/README.md
+++ b/README.md
@@ -101,6 +101,10 @@ The element is block-level and needs an explicit height.
| viewer‑css‑theme | Viewer theme: `AUTOMATIC`, `LIGHT`, `DARK`. | `AUTOMATIC` |
| assets‑base | Directory URL of the viewer runtime files (`viewer.mjs`, `pdf.mjs`, `pdf.worker.min.mjs`, `viewer.css`, `paper-and-ink.css`). Only needed when your bundler does not handle `new URL('./file', import.meta.url)`, see [Bundlers](#bundlers-and-assets-base). | folder of `pdfjs-viewer-element.js` |
| worker‑src | PDF.js worker URL override. | bundled worker (`./build/pdf.worker.mjs` in dev, `pdf.worker.min.mjs` next to the element or in `assets-base`) |
+| pdfjs‑lib‑src | PDF.js library module URL override. | bundled `pdf.mjs`, or `assets-base + pdf.mjs` |
+| viewer‑src | PDF.js viewer module URL override. | bundled `viewer.mjs`, or `assets-base + viewer.mjs` |
+| viewer‑css‑src | PDF.js viewer stylesheet URL override. | bundled `viewer.css`, or `assets-base + viewer.css` |
+| theme‑css‑src | Paper & Ink theme stylesheet URL override. | bundled `paper-and-ink.css`, or `assets-base + paper-and-ink.css` |
| debugger‑src | PDF.js debugger script URL (`debuggerSrc` option). | `./debugger.mjs` |
| c‑map‑url | CMap directory URL (`cMapUrl` option). | `../web/cmaps/` |
| icc‑url | ICC profile directory URL (`iccUrl` option). | `../web/iccs/` |
@@ -120,7 +124,7 @@ Most attributes can be updated dynamically:
- `viewer-css-theme` updates the viewer theme at runtime.
- `worker-src`, `debugger-src`, `c-map-url`, `icc-url`, `image-resources-path`, `sandbox-bundle-src`, `standard-font-data-url`, `wasm-url` update viewer options for subsequent document loads.
- `locale` rebuilds the viewer so localization resources can be applied.
-- `assets-base` and `locale-src-template` are read when the viewer is (re)initialized, so set them before the element is attached.
+- `assets-base`, `pdfjs-lib-src`, `viewer-src`, `viewer-css-src`, `theme-css-src` and `locale-src-template` are read when the viewer is (re)initialized, so set them before the element is attached. Changing them alone does not reload the viewer.
## Bundlers and assets base
@@ -136,7 +140,7 @@ Bundlers that do not analyse `new URL(..., import.meta.url)`, esbuild among them
```
- The value is a directory URL, relative to the page or absolute. A trailing slash is added when missing.
-- `worker-src` still takes precedence for the worker file.
+- Individual runtime URL attributes take precedence over `assets-base` for their respective files, including `worker-src`.
- The attribute is read when the viewer is (re)initialized, so set it before the element is attached to the document.
PDF.js annotation icons are a separate case: the viewer loads them at runtime as `imageResourcesPath + 'annotation-' + name + '.svg'`, a dynamic name no bundler can emit. If `assets-base` is set, `imageResourcesPath` defaults to `assets-base + 'images/'`. Otherwise serve the package `dist/images` folder and set `image-resources-path` explicitly.
diff --git a/demo/asset-attributes.html b/demo/asset-attributes.html
new file mode 100644
index 0000000..7e86dae
--- /dev/null
+++ b/demo/asset-attributes.html
@@ -0,0 +1,55 @@
+
+
+
+
+
+ pdfjs-viewer-element | Static file attributes demo
+
+
+
+
+
+
+
+
+
+
diff --git a/src/pdfjs-viewer-element.ts b/src/pdfjs-viewer-element.ts
index f807eeb..b557e34 100644
--- a/src/pdfjs-viewer-element.ts
+++ b/src/pdfjs-viewer-element.ts
@@ -32,7 +32,7 @@ const DEFAULT_PAPER_AND_INK_THEME_CSS_SRC = import.meta.env.DEV
// File names inside the package `dist` folder, used when `assets-base` is set.
const DIST_FILE_NAMES = {
workerSrc: 'pdf.worker.min.mjs',
- pdfSrc: 'pdf.mjs',
+ pdfjsLibSrc: 'pdf.mjs',
viewerSrc: 'viewer.mjs',
viewerCssSrc: 'viewer.css',
themeCssSrc: 'paper-and-ink.css'
@@ -42,7 +42,7 @@ type RuntimeFileUrls = Record
const DEFAULT_RUNTIME_FILE_URLS: RuntimeFileUrls = {
workerSrc: DEFAULT_BUILT_IN_WORKER_SRC,
- pdfSrc: DEFAULT_PDF_SRC,
+ pdfjsLibSrc: DEFAULT_PDF_SRC,
viewerSrc: DEFAULT_VIEWER_SRC,
viewerCssSrc: DEFAULT_VIEWER_CSS_SRC,
themeCssSrc: DEFAULT_PAPER_AND_INK_THEME_CSS_SRC
@@ -140,9 +140,13 @@ export class PdfjsViewerElement extends HTMLElement {
// Read when the viewer is (re)built, so the attribute must be present before connection.
private getRuntimeFileUrls(): RuntimeFileUrls {
const assetsBase = this.getAssetsBase()
- if (!assetsBase) return DEFAULT_RUNTIME_FILE_URLS
- const resolveFile = (fileName: string) => {
+ const resolveFile = (field: keyof typeof DIST_FILE_NAMES) => {
+ const attr = this.getAttribute(field.replace(/[A-Z]/g, letter => `-${letter.toLowerCase()}`))?.trim()
+ if (attr) return this.getFullPath(attr)
+ if (!assetsBase) return DEFAULT_RUNTIME_FILE_URLS[field]
+
+ const fileName = DIST_FILE_NAMES[field]
try {
return new URL(fileName, assetsBase).href
} catch {
@@ -151,18 +155,14 @@ export class PdfjsViewerElement extends HTMLElement {
}
return {
- workerSrc: resolveFile(DIST_FILE_NAMES.workerSrc),
- pdfSrc: resolveFile(DIST_FILE_NAMES.pdfSrc),
- viewerSrc: resolveFile(DIST_FILE_NAMES.viewerSrc),
- viewerCssSrc: resolveFile(DIST_FILE_NAMES.viewerCssSrc),
- themeCssSrc: resolveFile(DIST_FILE_NAMES.themeCssSrc)
+ workerSrc: resolveFile('workerSrc'),
+ pdfjsLibSrc: resolveFile('pdfjsLibSrc'),
+ viewerSrc: resolveFile('viewerSrc'),
+ viewerCssSrc: resolveFile('viewerCssSrc'),
+ themeCssSrc: resolveFile('themeCssSrc')
}
}
- private getWorkerSrc() {
- return this.getAttribute('worker-src') || this.getRuntimeFileUrls().workerSrc
- }
-
private getImageResourcesPath() {
const imageResourcesPath = this.getAttribute('image-resources-path')
if (imageResourcesPath) return imageResourcesPath
@@ -383,7 +383,7 @@ export class PdfjsViewerElement extends HTMLElement {
private applyViewerOptions = () => {
const viewerOptions = this.iframe.contentWindow?.PDFViewerApplicationOptions
- viewerOptions?.set('workerSrc', this.getWorkerSrc())
+ viewerOptions?.set('workerSrc', this.getRuntimeFileUrls().workerSrc)
viewerOptions?.set('debuggerSrc', this.getAttribute('debugger-src') || DEFAULTS.debuggerSrc)
viewerOptions?.set('cMapUrl', this.getAttribute('c-map-url') || DEFAULTS.cMapUrl)
viewerOptions?.set('iccUrl', this.getAttribute('icc-url') || DEFAULTS.iccUrl)
@@ -432,13 +432,13 @@ export class PdfjsViewerElement extends HTMLElement {
const toSourceList = (sources: string[]) =>
Array.from(new Set(sources.filter(Boolean))).join(' ')
- const { pdfSrc, viewerSrc, viewerCssSrc, themeCssSrc } = this.getRuntimeFileUrls()
- const workerSrcOrigin = resolveHttpOrigin(this.getWorkerSrc())
+ const { pdfjsLibSrc, viewerSrc, viewerCssSrc, themeCssSrc, workerSrc } = this.getRuntimeFileUrls()
+ const workerSrcOrigin = resolveHttpOrigin(workerSrc)
const scriptSources = toSourceList([
"'self'",
window.location.origin,
- resolveHttpOrigin(pdfSrc),
+ resolveHttpOrigin(pdfjsLibSrc),
resolveHttpOrigin(viewerSrc),
workerSrcOrigin
])
@@ -508,8 +508,8 @@ export class PdfjsViewerElement extends HTMLElement {
await this.injectLocaleData()
- const { pdfSrc, viewerSrc } = this.getRuntimeFileUrls()
- await this.injectScript(pdfSrc)
+ const { pdfjsLibSrc, viewerSrc } = this.getRuntimeFileUrls()
+ await this.injectScript(pdfjsLibSrc)
await this.injectScript(viewerSrc)
return await setupPromise
@@ -547,7 +547,7 @@ export class PdfjsViewerElement extends HTMLElement {
if (name === 'worker-src') {
const viewerOptions = this.iframe.contentWindow?.PDFViewerApplicationOptions
- viewerOptions?.set('workerSrc', newValue || this.getRuntimeFileUrls().workerSrc)
+ viewerOptions?.set('workerSrc', this.getRuntimeFileUrls().workerSrc)
return
}
diff --git a/tests/basic.test.ts b/tests/basic.test.ts
index c5d6917..7127e97 100644
--- a/tests/basic.test.ts
+++ b/tests/basic.test.ts
@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest'
import { getFileData, getViewerElement, mountViewer, getIframe } from './utils'
-import '../src/pdfjs-viewer-element'
+import { PdfjsViewerElement } from '../src/pdfjs-viewer-element'
describe('Basic tests', async () => {
it('should render the PDF file', async () => {
@@ -108,11 +108,12 @@ describe('Basic tests', async () => {
)
const viewer = document.body.querySelector('pdfjs-viewer-element') as HTMLElement
- const workerSrc = 'https://example.com/pdf.worker.min.mjs'
- viewer.setAttribute('worker-src', workerSrc)
+ for (const workerSrc of ['https://example.com/pdf.worker.min.mjs', '/static/pdfjs/pdf.worker.abc.mjs', './pdf.worker.mjs']) {
+ viewer.setAttribute('worker-src', workerSrc)
- const options = getIframe().contentWindow.PDFViewerApplicationOptions.getAll()
- expect(options.workerSrc).eq(workerSrc)
+ const options = getIframe().contentWindow.PDFViewerApplicationOptions.getAll()
+ expect(options.workerSrc).eq(new URL(workerSrc, location.href).href)
+ }
})
it('should include external worker origin in CSP script and worker directives', async () => {
@@ -258,6 +259,29 @@ describe('Basic tests', async () => {
}
})
+ it('should load individual runtime URLs without assets-base', async () => {
+ await mountViewer(`
+
+ `
+ )
+
+ const doc = getIframe().contentDocument!
+ for (const path of ['/src/build/pdf.mjs', '/src/web/viewer.mjs']) {
+ expect(doc.querySelector(`script[src="${location.origin}${path}?manifest=123"]`)).exist
+ }
+ for (const path of ['/src/web/viewer.css', '/src/themes/paper-and-ink.css']) {
+ expect(doc.querySelector(`link[href="${location.origin}${path}?manifest=123"]`)).exist
+ }
+ expect(getIframe().contentWindow.PDFViewerApplicationOptions.getAll().workerSrc)
+ .eq(new URL('/src/build/pdf.worker.mjs?manifest=123', location.href).href)
+ expect(getViewerElement()).exist
+ })
+
it('should open a new document when src changes at runtime', async () => {
const viewerApp = await mountViewer(`
`
@@ -282,4 +306,40 @@ describe('Basic tests', async () => {
viewerApp.open = originalOpen
}
})
-})
\ No newline at end of file
+})
+
+describe('Runtime file URL overrides', () => {
+ const files = {
+ workerSrc: ['worker-src', 'pdf.worker.min.mjs'],
+ pdfjsLibSrc: ['pdfjs-lib-src', 'pdf.mjs'],
+ viewerSrc: ['viewer-src', 'viewer.mjs'],
+ viewerCssSrc: ['viewer-css-src', 'viewer.css'],
+ themeCssSrc: ['theme-css-src', 'paper-and-ink.css']
+ } as const
+
+ it.each([undefined, '/static/pdfjs'])('resolves individual URLs with assets-base=%s', (assetsBase) => {
+ const viewer = new PdfjsViewerElement()
+ if (assetsBase) viewer.setAttribute('assets-base', assetsBase)
+ const defaults = viewer['getRuntimeFileUrls']()
+
+ for (const [key, [attribute, file]] of Object.entries(files)) {
+ const field = key as keyof typeof files
+ const path = `/static/hashed/${file}?v=123`
+ viewer.setAttribute(attribute, path)
+ expect(viewer['getRuntimeFileUrls']()[field]).eq(new URL(path, location.href).href)
+ viewer.setAttribute(attribute, 'https://cdn.example.com/' + file)
+ expect(viewer['getRuntimeFileUrls']()[field]).eq('https://cdn.example.com/' + file)
+ viewer.setAttribute(attribute, '')
+ expect(viewer['getRuntimeFileUrls']()[field]).eq(defaults[field])
+ viewer.setAttribute(attribute, ' \t\n ')
+ expect(viewer['getRuntimeFileUrls']()[field]).eq(defaults[field])
+ viewer.setAttribute(attribute, ` ${path} `)
+ expect(viewer['getRuntimeFileUrls']()[field]).eq(new URL(path, location.href).href)
+ viewer.removeAttribute(attribute)
+ expect(viewer['getRuntimeFileUrls']()).toEqual(defaults)
+ if (assetsBase) {
+ expect(defaults[field]).eq(new URL(`${assetsBase}/${file}`, location.href).href)
+ }
+ }
+ })
+})
diff --git a/types/pdfjs-viewer-element.d.ts b/types/pdfjs-viewer-element.d.ts
index f0668e2..ec81124 100644
--- a/types/pdfjs-viewer-element.d.ts
+++ b/types/pdfjs-viewer-element.d.ts
@@ -32,7 +32,6 @@ export declare class PdfjsViewerElement extends HTMLElement {
private getFullPath;
private getAssetsBase;
private getRuntimeFileUrls;
- private getWorkerSrc;
private getImageResourcesPath;
private getCssThemeOption;
private applyIframeHash;