Blog Post

How to Build a TypeScript PDF Viewer with PDF.js

Illustration: How to Build a TypeScript PDF Viewer with PDF.js
Information

This article was first published in September 2021 and was updated in April 2022.

In this tutorial, we’ll show how to build a TypeScript PDF viewer with PDF.js, one of the most popular open source libraries for rendering PDF files in the browser.

Results for The State of JS 2021 were recently published, and TypeScript is by far the most common alternative flavor of JavaScript. Developers seem to like TypeScript due to its type safety and the fact that it’s a superset of JavaScript. TypeScript adds static typing to JavaScript, which allows developers to catch errors at compile time instead of run time.

In the first part of this tutorial, we’ll walk through how to render a PDF in the browser with PDF.js and TypeScript. In the second part, we’ll look at how to build a fully featured PDF viewer with the PSPDFKit TypeScript PDF library. Our PDF viewer library provides some additional benefits beyond what PDF.js provides, including:

  • A prebuilt and polished UI
  • Annotation tools
  • Support for multiple file types
  • 30+ out-of-the-box features
  • Dedicated support

Requirements

To get started, you’ll need:

  • Node.js

  • A package manager for installing the PSPDFKit library. You can use npm or Yarn. When you install Node.js, npm is installed by default.

  • TypeScript

You can install TypeScript globally by running the following command:

npm install -g typescript

Building a TypeScript PDF Viewer with PDF.js

PDF.js is a JavaScript library built by Mozilla, and it allows you to create a full-featured PDF viewer in the browser using JavaScript and the HTML5 <canvas> element. You can integrate PDF.js with different JavaScript frameworks and libraries like React, Angular, and Vue.js.

Getting Started

  1. Create a new folder on your computer and change your directory to the project:

mkdir typescript-pdf-viewer

cd typescript-pdf-viewer
  1. Next, run npm init --yes to create a package.json file.

  2. Create a new tsconfig.json configuration file at the root of your project:

tsc --init

You can customize the rules you want the TypeScript compiler to follow, such as the following configuration:

// tsconfig.json
{
	"compilerOptions": {
		"target": "es6",
		"module": "es6",
		"esModuleInterop": true,
		"forceConsistentCasingInFileNames": true,
		"strict": true,
		"skipLibCheck": true,
		"removeComments": true,
		"preserveConstEnums": true,
		"sourceMap": true,
		"noImplicitAny": true,
		"strictNullChecks": false,
		"moduleResolution": "node"
	},
	"include": ["src/**/*"]
}

With this configuration, the compiled JavaScript code will target the 2015 version of ECMAScript standards for JavaScript, and it’ll use the ES6 import and export modules. include determines what files the compiler will process. In this case, it’ll check every file in the src folder.

Check out the compiler options for more information.

Installing PDF.js and Configuring webpack

  1. You’ll use one of the most popular build tools, webpack, to bundle your project into a single file. It’ll help you reduce the HTTP requests needed to load the PDF and minify your code.

Start by downloading the necessary dev dependencies:

npm i -D webpack webpack-cli webpack-dev-server ts-loader typescript html-webpack-plugin cross-env copy-webpack-plugin clean-webpack-plugin

Here’s what’s installed:

  • webpack — The webpack bundler.

  • webpack-cli — Command-line interface for webpack.

  • webpack-dev-server — A local server to run webpack in the browser.

  • ts-loader — A package that teaches webpack how to compile TypeScript.

  • typescript — The TypeScript compiler.

  • clean-webpack-plugin — A plugin that cleans the output directory before building.

  • copy-webpack-plugin — A plugin that copies files and directories to the output directory.

  • html-webpack-plugin — A plugin that generates an HTML file from a template.

  • cross-env — A package that allows you to set environment variables.

After the installation, you can see the dependencies in your package.json file:

"devDependencies": {
    "clean-webpack-plugin": "^4.0.0",
    "copy-webpack-plugin": "^10.2.4",
    "cross-env": "^7.0.3",
    "html-webpack-plugin": "^5.5.0",
    "ts-loader": "^9.2.7",
    "typescript": "^4.6.2",
    "webpack": "^5.70.0",
    "webpack-cli": "^4.9.2",
    "webpack-dev-server": "^4.7.4",
  }
  1. To configure webpack, create a webpack.config.js file in the root of your project. This will contain parameters that determine how the project will be built:

// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const CleanPlugin = require('clean-webpack-plugin');

module.exports = {
	entry: path.resolve(__dirname, './src/index.ts'),
	output: {
		path: path.resolve(__dirname, 'dist'),
		filename: '[name].js',
	},
	devtool: 'inline-source-map',
	mode: 'development',
	module: {
		rules: [
			// All files with a `.ts` or `.tsx` extension will be handled by `ts-loader`.
			{
				test: /\.tsx?$/,
				use: { loader: 'ts-loader' },
				exclude: /node_modules/,
			},
		],
	},
	resolve: {
		extensions: ['.ts', '.tsx', '.js'],
	},
	plugins: [
		new CleanPlugin.CleanWebpackPlugin(),
		// Automatically insert <script src="[name].js"><script> into the page.
		new HtmlWebpackPlugin({
			template: './src/index.html',
		}),
		// Copy the PDF file and PDF.js worker to the output path.
		new CopyWebpackPlugin({
			patterns: [
				{
					from: './src/example.pdf',
					to: './example.pdf',
				},
				{
					from: './node_modules/pdfjs-dist/build/pdf.worker.js',
					to: './main.worker.js',
				},
			],
		}),
	],
};

The entry point of your application will be the src/index.ts file, and the output will be bundled inside the dist folder. Note that you’re copying both the PDF file you want to display and the PDF.js worker to the output path.

You created the module property containing the rules array to handle the TypeScript files. So, when webpack finds a file with the .ts or .tsx extension, it’ll use the ts-loader to compile it. Only then will the file be added to the dist build.

  1. There are multiple ways to install PDF.js. Here, you’ll install it as a dependency in your package.json file:

npm install pdfjs-dist

pdfjs-dist provides its own type definitions, so you don’t need to install any other dependency for the TypeScript support.

Rendering a PDF

After setting up your project with webpack and TypeScript, you can start interacting with the PDF.js library.

  1. Create the entry point of your application by creating a src directory and an index.html file:

mkdir src && touch src/index.html
  1. Create a <canvas> element, which you want the first page of the PDF to be rendered into:

<!DOCTYPE html>
<html lang="en">
	<head>
		<title>PDF.js Example</title>
	</head>
	<body>
		<canvas id="pdf"></canvas>
	</body>
</html>
  1. Now, create an index.ts file inside the src directory:

.ts is the extension used for TypeScript files. Later, you’ll run your code through the TypeScript transpiler to output a JavaScript (.js) version of the file:

import * as pdfjsLib from "pdfjs-dist";

(async () => {
  const loadingTask = pdfjsLib.getDocument("example.pdf");
  const pdf = await loadingTask.promise;

  // Load information from the first page.
  const page = await pdf.getPage(1);

  const scale = 1;
  const viewport = page.getViewport({ scale });

  // Apply page dimensions to the `<canvas>` element.
  const canvas = document.getElementById("pdf") as HTMLCanvasElement;
  const context = canvas.getContext("2d");
  canvas.height = viewport.height;
  canvas.width = viewport.width;

  // Render the page into the `<canvas>` element.
  const renderContext = {
    canvasContext: context,
    viewport: viewport,
  };
  await page.render(renderContext);
  console.log("Page rendered!");
})();

Here, you’re explicitly importing everything from the pdfjs-dist package and adding an interface for the HTMLCanvasElement.

  • Use the pdfjsLib.getDocument(pdf) method to load the PDF file, with the pdf parameter being the path of the PDF file. Then, use the .promise method to handle the promise.

  • From there, you can access a single page via the page(pageNumber) method (pageNumber starts at 1 for the first page, and so on).

  • To draw something on the canvas, use the canvas.getContext() method. This method returns a context object you can use to draw on the canvas.

  • The getViewport(scale) method returns a viewport object that represents the page at the given scale.

  • Use the viewport information to set the dimensions of the <canvas> element, and then start the page renderer with the render(options) API.

Running the Project

  1. To run the project, add some scripts to the package.json file:

"scripts": {
    "build": "cross-env NODE_ENV=production webpack --config webpack.config.js",
    "prestart": "npm run build",
    "dev": "tsc",
    "start": "serve -l 8080 ./dist"
},
  1. Add your PDF file inside the src directory before running the project. You can use our demo document as an example.

  2. Now, run npm start to start the server. Navigate to http://localhost:8080 to see the contents of the dist directory.

Resulting page

Information

You can access the full code on GitHub.

Building a TypeScript PDF Viewer with PSPDFKit

We offer a commercial TypeScript PDF library that can easily be integrated into your web application. It comes with 30+ features that let you view, annotate, edit, and sign documents directly in your browser. Out of the box, it has a polished and flexible UI that you can extend or simplify based on your unique use case.

Getting Started

  1. Create a new folder and change your directory to it:

mkdir typescript-pspdfkit-viewer

cd typescript-pspdfkit-viewer
  1. Similar to what you did above, create a package.json file by running npm init --yes.

  2. Create a tsconfig.json file and use the following configuration:

{
	"compilerOptions": {
		"removeComments": true,
		"preserveConstEnums": true,
		"module": "commonjs",
		"target": "es5",
		"sourceMap": true,
		"noImplicitAny": true,
		"esModuleInterop": true
	},
	"include": ["src/**/*"]
}

Installing PSPDFKit and Configuring webpack

  1. Install the PSPDFKit for Web library as a dependency with npm or yarn:

npm install pspdfkit
  1. Install the necessary dependencies for webpack, create a config directory, and place your webpack configuration file inside it:

mkdir config && touch config/webpack.js
  1. If you’re using webpack 4, use the example file. If you’re using the latest version of webpack — currently ^5.72.0 — use the following configuration:

// webpack.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');

const filesToCopy = [
	// PSPDFKit files.
	{
		from: './node_modules/pspdfkit/dist/pspdfkit-lib',
		to: './pspdfkit-lib',
	},
	// Application CSS.
	{
		from: './src/index.css',
		to: './index.css',
	},
	// Example PDF.
	{
		from: './assets/example.pdf',
		to: './example.pdf',
	},
];

/**
 * webpack main configuration object.
 */
const config = {
	entry: path.resolve(__dirname, '../src/index.ts'),
	mode: 'development',
	devtool: 'inline-source-map',
	output: {
		path: path.resolve(__dirname, '../dist'),
		filename: '[name].js',
	},
	resolve: {
		extensions: ['.ts', '.tsx', '.js'],
	},
	module: {
		rules: [
			// All files with a `.ts` or `.tsx` extension will be handled by `ts-loader`.
			{
				test: /\.tsx?$/,
				loader: 'ts-loader',
				exclude: /node_modules/,
			},
		],
	},
	plugins: [
		// Automatically insert <script src="[name].js"><script> into the page.
		new HtmlWebpackPlugin({
			template: './src/index.html',
		}),

		// Copy the WASM/ASM and CSS files to the `output.path`.
		new CopyWebpackPlugin({ patterns: filesToCopy }),
	],

	optimization: {
		splitChunks: {
			cacheGroups: {
				// Creates a `vendor.js` bundle that contains external libraries (including `pspdfkit.js`).
				vendor: {
					test: /node_modules/,
					chunks: 'initial',
					name: 'vendor',
					priority: 10,
					enforce: true,
				},
			},
		},
	},
};

module.exports = config;

Displaying the PDF

  1. Add the PDF document you want to display to the assets directory. You can use our demo document as an example.

  2. Create an index.html file inside the src directory and add the following code:

<!DOCTYPE html>
<html>
	<head>
		<title>PSPDFKit for Web — TypeScript example</title>
		<link rel="stylesheet" href="index.css" />
	</head>
	<body>
		<div class="container"></div>
	</body>
</html>

This adds an empty <div> element to where PSPDFKit will be mounted.

  1. Declare the height of this element in your CSS file like this:

.container {
	height: 100vh;
}
  1. Now, create an index.ts file inside the src directory:

import PSPDFKit from 'pspdfkit';

function load(document: string) {
	console.log(`Loading ${document}...`);
	PSPDFKit.load({
		document,
		container: '.container',
	})
		.then((instance) => {
			console.log('PSPDFKit loaded', instance);
		})
		.catch(console.error);
}

load('example.pdf');

Here, you’ve imported the PSPDFKit library and created a function that loads the PDF document.

Running the Project

  1. Now, write some scripts in the package.json file to start your server:

"scripts": {
    "build": "cross-env NODE_ENV=production webpack --config config/webpack.js",
    "prestart": "npm run build",
    "dev": "tsc",
    "start": "serve -l 8080 ./dist"
},
  1. Run npm start to start the server. Navigate to http://localhost:8080 to see the contents of the dist directory.

Resulting page

Opening Different PDF Files

The current viewer is loading the PDF file you have in the assets/example.pdf file. But you can also open different PDF files by adding an input field with the file type on the index.html file:

<body>
	<div>
		<input type="file" class="chooseFile" accept="application/pdf" />
	</div>
	<br />
	<div class="container"></div>
</body>

Now, go to the index.ts file and add the following code:

interface HTMLInputEvent extends Event {
	target: HTMLInputElement & EventTarget;
}

let objectUrl = '';

document.addEventListener('change', function (event: HTMLInputEvent) {
	if (
		event.target &&
		event.target.className === 'chooseFile' &&
		event.target.files instanceof FileList
	) {
		PSPDFKit.unload('.container');

		if (objectUrl) {
			URL.revokeObjectURL(objectUrl);
		}

		objectUrl = URL.createObjectURL(event.target.files[0]);
		load(objectUrl);
	}
});

Here, you’ve added a change event listener to the <input> element. When the user selects a file, you’ll unload the current PDF and load the new one.

Information

If you want to learn how to add annotations to your application, check out the Open and Annotate PDFs in a TypeScript App blog post. You can see the example project with annotation support on GitHub.

Screenshot showing Choose File Example

Adding Even More Capabilities

Once you’ve deployed your viewer, you can start customizing it to meet your specific requirements or easily add more capabilities. To help you get started, here are some of our most popular TypeScript guides:

Conclusion

In this tutorial, you saw how to build a TypeScript PDF viewer — first with PDF.js, and then with the PSPDFKit TypeScript PDF viewer library that allows you to display and render PDF files in your TypeScript application.

We created similar how-to blog posts using different web frameworks and libraries:

To get started with our TypeScript PDF viewer, try it for free, or launch our web demo.

Related Products
Share Post
Free 60-Day Trial Try PSPDFKit in your app today.
Free Trial

Related Articles

Explore more
PRODUCTS  |  Web • Releases • Components

PSPDFKit for Web 2022.4 Renders Media Annotations

TUTORIALS  |  Web • Remix • React • JavaScript • How To • PDF Viewer

How to Build a PDF Viewer with Remix and PSPDFKit

WEB  |  Web • Vue.js • JavaScript • How to • PDF Generation

Generate a PDF from HTML with Vue.js