pdfmake 生成字体
最近,我为一个客户开发了一项功能,该功能涉及从他的Angular Web应用程序生成PDF文档。 经过研究,我们决定为此目的使用PDFMake 。 (Recently I worked on a feature for a client that involved generating a PDF document from his Angular web application. After some research we decided to use PDFMake for this purpose.)
PDFMake is an excellent Javascript library for generating PDF documents. This short post is going to discuss how we can integrate the library with an Angular 9 app in a way that does not increase our initial bundle size!
PDFMake是用于生成PDF文档的优秀Javascript库。 这篇简短的文章将讨论如何以不增加初始捆绑包大小的方式将库与Angular 9应用程序集成!
为什么选择PDFMake? (Why PDFMake?)
We chose PDFMake because it allows us to specify the data for generation using a document definition object format. Other libraries required absolute positioning to position our content in the document. Since we had quite a lot of data and formatting to do, having an easier format saved us a lot of time!
我们之所以选择PDFMake,是因为它允许我们使用文档定义对象格式指定要生成的数据。 其他库需要绝对定位才能在文档中定位我们的内容。 由于我们要处理大量数据和格式化,因此使用更简单的格式可以节省大量时间!
To know more about PDFMake’s document definition object format, please refer to their official documentation.
要了解有关PDFMake文档定义对象格式的更多信息,请参阅其官方文档。
With this out of the way, let’s start integrating the library into an Angular app!
顺便说一句,让我们开始将库集成到Angular应用中!
设定 (Setting it up)
First, we’ll create a new Angular 9 app by executing the following commands in our terminal or console.
首先,我们将通过在终端或控制台中执行以下命令来创建新的Angular 9应用程序。
ng new angular-pdf-generator --routing=false
Let’s also add the Angular Material library, so we can use a material button to allow the user to generate the PDF.
我们还添加Angular Material库,以便我们可以使用Material按钮来允许用户生成PDF。
ng add @angular/material
Next, let’s include the required material modules in our app.module.ts file.
接下来,让我们在app.module.ts文件中包含必需的材料模块。
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatButtonModule } from '@angular/material/button';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
BrowserAnimationsModule,
MatToolbarModule,
MatButtonModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Finally, here is a simple UI for our purposes.
最后,这是一个简单的用户界面。
<mat-toolbar color="primary">
PDF Generator with PDFMake
</mat-toolbar>
<button mat-raised-button color="primary">
Generate PDF
</button>
A toolbar and a button will do the trick for now!
工具栏和按钮将立即解决问题!

将PDFMake添加到应用程序 (Adding PDFMake to the app)
Let’s install PDFMake now from npm using the following command.
现在,使用以下命令从npm安装PDFMake。
npm install pdfmake --save
For a better structure to our app, let’s encapsulate all of our PDF generation functions inside of a service. We can create that using the following command.
为了使我们的应用程序结构更好,让我们将所有PDF生成功能封装在服务中。 我们可以使用以下命令创建它。
ng generate service pdf
静态导入及其对应用程序包大小的影响 (Static import and its effect on app bundle size)
At this point, the typical way to include a Javascript library would be to use a static import statement at the top of our service file and then use the imported object for PDF generation. For PDFMake, it can be as follows.
此时,包含Javascript库的典型方法是在服务文件顶部使用静态import语句,然后将导入的对象用于PDF生成。 对于PDFMake,可以如下所示。
import pdfMake from 'pdfmake/build/pdfmake';
import pdfFonts from 'pdfmake/build/vfs_fonts';pdfMake.vfs = pdfFonts.pdfMake.vfs;
However, PDFMake is big library with a bundle size of approximately 3.7 MB. If we use the above approach, it is going to make our app bundle size unnecessarily large, even if only a part of our app needs the PDF generation. With an increasing focus on web performance nowadays, this would be an unwise move!
但是,PDFMake是一个大型库,捆绑包大小约为3.7 MB。 如果我们使用上述方法,即使我们的应用程序只有一部分需要生成PDF,它也会使应用程序包的尺寸不必要地变大。 如今,随着人们越来越关注网络性能,这将是不明智的举动!
将延迟加载添加到我们的应用 (Add Lazy Loading to our app)
To handle such cases, Angular 9 allows us to use lazy loading for our packages. Lazy loading means we’re only going to load our PDFMake library when we actually need it. In our case we’re going to do it on our button click event.
为了处理这种情况,Angular 9允许我们对包使用延迟加载。 延迟加载意味着我们仅在实际需要时才加载PDFMake库。 在我们的案例中,我们将在按钮单击事件中执行此操作。
For lazy loading, we can use webpack’s awesome new dynamic import feature. Dynamic imports load our package on the fly and resolves to a Promise — so we can continue our work using the package just loaded.
对于延迟加载,我们可以使用webpack很棒的新动态导入功能。 动态导入会动态加载我们的程序包并解决为Promise-因此我们可以使用刚刚加载的程序包继续我们的工作。
Let’s see how we add it to our PDF service.
让我们看看如何将其添加到我们的PDF服务中。
export class PdfService {
pdfMake: any;
constructor() { }
async loadPdfMaker() {
if (!this.pdfMake) {
const pdfMakeModule = await import('pdfmake/build/pdfmake');
const pdfFontsModule = await import('pdfmake/build/vfs_fonts');
this.pdfMake = pdfMakeModule.default;
this.pdfMake.vfs = pdfFontsModule.default.pdfMake.vfs;
}
}
async generatePdf() {
await this.loadPdfMaker();
const def = { content: 'A sample PDF document generated using Angular and PDFMake' };
this.pdfMake.createPdf(def).open();
}
}
让我们回顾一下我们在这里所做的事情。(Let’s go over what we did here.)
First, we created a variable in our service to contain the pdfMake reference when we load it using the import statement.
首先,当我们使用import语句加载pdfMake引用时,我们在服务中创建了一个变量。
Then we create an async function to load the PDFMake library. We use two dynamic imports because fonts for PDFMake also need to be imported and set for it to work properly. Since both resolve to a Promise, we can use await keyword to wait on the imports before moving forward in the execution.
然后,我们创建一个异步函数来加载PDFMake库。 我们使用两个动态导入,因为PDFMake的字体也需要导入并设置才能正常工作。 由于两者都解决了Promise问题,因此我们可以使用await关键字等待导入,然后再继续执行。
Lastly, we just use the load function inside of our main generatePdf function, so that our library is loaded before we go on using it!
最后,我们只在主要的generatePdf函数内部使用load函数,以便在继续使用库之前先对其进行加载!
We’re using a simple definition object here. PDFMake provides us a lot of options to create well formatted PDF documents, including with images!
我们在这里使用一个简单的定义对象。 PDFMake为我们提供了许多创建格式良好的PDF文档(包括图像)的选项!
For more details refer to their official documentation.
有关更多详细信息,请参阅其官方文档。
从我们的组件调用服务 (Calling the service from our component)
As a last step, let’s add the code to call our service from our component (after linking it with the button).
最后,让我们添加代码以从组件调用我们的服务(将其与按钮链接之后)。
export class AppComponent {
constructor(private pdfService: PdfService) {
}
generatePdf() {
this.pdfService.generatePdf();
}
}
测试全部!(Testing it all out!)
Let’s do ng serve
and quickly test this out. Let’s open up our Developer Tools on the Network tab as well.
让我们来ng serve
并快速进行测试。 让我们也在“网络”选项卡上打开“开发人员工具”。
When we run our app now and click the ‘Generate PDF’ button, we should see the PDF generated and opened up in a new tab. Cool!
当我们现在运行我们的应用程序并单击“生成PDF”按钮时,我们应该在新选项卡中看到生成并打开的PDF。 凉!
What’s more, if we look closely at the Network tab, we’ll see the following two libraries were lazy loaded when we clicked the button. Check out their sizes!
更重要的是,如果我们仔细查看“网络”选项卡,我们将在单击按钮时看到以下两个库被延迟加载。 查看它们的尺寸!

This is exactly what we wanted! Our app’s initial size remains the same and we only load the PDFMake library when we need to — which saves us a hefty 3.7 MB in bundle size.
这正是我们想要的! 我们的应用程序的初始大小保持不变,并且仅在需要时才加载PDFMake库,这为我们节省了3.7 MB的捆绑包大小。
如何运作 (How this works)
This works due to a webpack feature called dynamic imports. As soon as webpack notices an import statement in your code with a package name inside it, it will automatically create a separate bundle file for that package during compilation.
这是由于Webpack功能(称为动态导入)而起作用的。 一旦webpack在代码中发现其中包含包名称的import语句,它将在编译过程中自动为该包创建一个单独的捆绑文件。
This will then be used for lazy loading at runtime. Without us having to do anything else. Sweet!
然后,它将在运行时用于延迟加载。 无需我们做任何其他事情。 甜!
If you’d like to read up more on this webpack feature, here is the official documentation.
如果您想了解更多有关此Webpack功能的信息,请参阅官方文档。
结论 (Conclusion)
As you can see, it is now quite easy and convenient for us to lazy load our PDFMake package when we feel like at runtime in our Angular app.
如您所见,当我们感觉像在运行时在Angular应用程序中一样,现在懒惰加载PDFMake包对我们来说非常容易和方便。
This is not limited to PDF generation though and opens up a lot of possibilities for improving our app’s load performance in general as well. The less your initial bundle size, the better it is for your Lighthouse score (which is getting increasingly important nowadays).
但是,这不仅限于PDF生成,而且还为改善我们应用程序的加载性能提供了很多可能性。 初始捆绑包大小越小,它的Lighthouse得分就越好(如今它变得越来越重要)。
I hope you found this useful. Post a comment below if you want to share something interesting!
希望您觉得这有用。 如果您想分享一些有趣的东西,请在下面发表评论!
The code for this tutorial is available on the following github repository.
本教程的代码可在以下github存储库中找到。
Thanks for reading! Bye
谢谢阅读! 再见
This story was original published on zoaibkhan.com.
这个故事最初是在zoaibkhan.com上发布的。
翻译自: https://medium.com/javascript-in-plain-english/generate-pdfs-in-angular-with-pdfmake-732557c91e9e
pdfmake 生成字体