Pete
Pete

Reputation: 10918

Vue: use a custom libary (pdf.js) in a component

How can I use a vendor libary (specifically I want to use PDF.js) in a Vue component? (I only want to load it for this specific component as they are rather larger files)

I'm building an editor that needs to load a pdf. So I placed the pdf.js and pdf.worker.js in /src/assets/vendor/pdfjs

Then I load both in the template-editor-page.hbs that also loads the component:

<div class="content">
  <div class="row fill">
    <div class="col-md-2 fill br pt30">
    </div>
    <div class="col-md-10 fill pt30 pl30 pr30">
      <div id="template-editor" class="template-editor">  
        <template-editor template-src="{{template.src}}"></template-editor>    
      </div>
    </div>
  </div>
</div>
<script src="/assets/js/template-editor.bundle.js"></script>
<script src="/assets/vendor/pdfjs/pdf.js"></script>
<script src="/assets/vendor/pdfjs/pdf.worker.js"></script>

my template-editor.js (do I have to load it here?):

import Vue from 'vue';
import templateEditor from './components/template-editor.vue';

new Vue({
  el: '#template-editor',
  components: { templateEditor }
});

Now I want to load the file in my template-editor.vue:

<template>
    <!-- ... -->
</template>

<script>

  export default {
    props: ['templateSrc'],
    name: 'template-editor',
    data() {
      return {
        src: this.templateSrc
      };
    },
    methods: {
      render() {
        PDFJS.getDocument(this.$data.src).then(function(pdf) {
          console.log(pdf);
        }, err => console.log(err));
      }
    },
    created: function() {
      this.render();
    }
  };
</script>

But I get an error saying

ReferenceError: PDFJS is not defined

Everything else seems to be working out fine. What am I missing?

Upvotes: 2

Views: 7803

Answers (4)

AymKdn
AymKdn

Reputation: 3927

I have an old project with Webpack 4, Babel and Vue 2. It has been painful to use the pdf.js library from Mozilla.

I ended to create a private Vue Component called 'pdf-viewer':

<template>
  <div ref="pdfrender" class="pdfrender"></div>
</template>

<script>
export default {
  emits:['rendered'],
  props:{
    libSrc:{ // the pdf.js version to use
      type:String,
      default:"https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.3.122/pdf.min.js"
    },
    pdfData:{ // a format supportd by pdf.js with "getDocument()" function – e.g. a buffer
      required:true,
      type:null
    },
    viewPortOptions:{ // the options to pass to `page.getViewport()`
      type:Object,
      default:() => ({scale:1})
    }
  },
  data () {
    return {
      pdfjsLib:null
    }
  },
  methods:{
    renderPage (page, num) {
      return new Promise(pr => {
        let viewport = page.getViewport(this.viewPortOptions);
        let wrapper = document.createElement("div");
        let canvas = document.createElement('canvas');
        let ctx = canvas.getContext('2d');
        let renderContext = {
          canvasContext: ctx,
          viewport: viewport
        };

        canvas.height = viewport.height;
        canvas.width = viewport.width;
        wrapper.appendChild(canvas);
        this.$refs.pdfrender.appendChild(wrapper);

        page.render(renderContext).promise.then(() => {
          console.info(`[pdf-viewer] Page #${num} rendered.`);
          pr();
        });
      })
    },
    renderPages (pdfDoc) {
      let arrProm = [];
      for(let num = 1; num <= pdfDoc.numPages; num++) arrProm.push(pdfDoc.getPage(num).then(page => this.renderPage(page, num)));
      Promise.all(arrProm)
      .then(() => {
        console.info("[pdf-viewer] All pages rendered.");
        // inform the parent when the PDF has rendered
        this.$emit('rendered');
      })
    },
    renderPDF() {
      this.pdfjsLib.getDocument(this.pdfData).promise.then(this.renderPages);
    },
    waitForPdfLib () {
      // we wait for the library to be loaded in the browser
      return new Promise(pr => {
        if (!window['pdfjs-dist/build/pdf']) {
          setTimeout(() => this.waitForPdfLib().then(pr), 1000);
        } else {
          this.pdfjsLib=window['pdfjs-dist/build/pdf'];
          this.pdfjsLib.GlobalWorkerOptions.workerSrc = this.libSrc.replace(/pdf.(min.)?js$/, "pdf.worker.$1js");
          pr();
        }
      })
    }
  },
  created () {
    // make sure the PDF.js is not loaded yet
    if (!window['pdfjs-dist/build/pdf']) {
      let tag = document.head.querySelector(`[src="${this.libSrc}"]`);
      if (!tag) {
        tag = document.createElement("script");
        tag.setAttribute("src", this.libSrc);
        tag.setAttribute("type", "text/javascript");
        document.head.appendChild(tag);
      }
    }
    this.waitForPdfLib()
    .then(() => this.renderPDF())
  }
}
</script>

<style>
.pdfrender {
  overflow: auto;
  height: calc(100vh - 250px);
  width: 850px;
}
.pdfrender > div {
  text-align: center;
  background-color: #EEE;
}
</style>

Then to call it:

<template>
  <div id="app">
    <div v-if="!pdfRendered">Loading the PDF...</div>
    <pdf-viewer :pdf-data="pdfData" @rendered="pdfRendered=true" v-if="pdfData"></pdf-viewer>
  </div>
<template>

<script>
import PDFComponent from './pdf-viewer.vue'

export default {
  components:{
    'pdf-viewer':PDFComponent
  },
  data () {
    return {
      pdfData:'',
      pdfRendered:false
    }
  },
  created () {
    this.pdfData="whatever format supported by getDocument()"; // see https://github.com/mozilla/pdf.js/blob/3f33fbf8cf1d7eb5f29de32288ebaa4dd4922501/src/display/api.js#L242
  }
}
</script>

Upvotes: 1

Pete
Pete

Reputation: 10918

Thank's for your help guys. Turns out the answer was hidden in the first snippet: I import the pdfjs AFTER the bundle. But since the bundle needs it, I need to import it BEFORE:

<script src="/assets/vendor/pdfjs/pdf.js"></script>
<script src="/assets/vendor/pdfjs/pdf.worker.js"></script>
<script src="/assets/js/template-editor.bundle.js"></script>

Really not all that complicated after all ;)

Upvotes: -1

Florian Haider
Florian Haider

Reputation: 1912

Instead of script tags for your vendor scripts, better use webpacks dynamic import feature (https://webpack.js.org/guides/code-splitting/#dynamic-imports) to load this vendor library in your render function:

render() {
    import('/assets/vendor/pdfjs/pdf.js').then(PDFJS => {
        PDFJS.getDocument(this.$data.src).then(function(pdf) {
          console.log(pdf);
        }, err => console.log(err));
    }
}

For import to work you will also have to install this babel plugin http://babeljs.io/docs/plugins/syntax-dynamic-import/.

Upvotes: 1

Richard Matsen
Richard Matsen

Reputation: 23483

I think all that's missing is an import statement in your component,

CORRECTION Try with an '@' in the import location below. I forgot, your component is probably in a sub-folder of 'src'. Also see note below about pdfjs-dist.

<script>
  import { PDFJS } from '@/assets/vendor/pdfjs/pdf.js'

  export default {
    props: ['templateSrc'],
    name: 'template-editor',
    ...

Alternative

Since you have webpack, you might be better off installing pdfjs-dist into node modules (see pdfjs-dist), and removing it from './assets/vendor/pdfjs/pdj.js'

npm install pdfjs-dist

If you do this, the import is more 'standard',

import { PDFJS } from 'pdfjs-dist'

Upvotes: 3

Related Questions