Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use with node.js #55

Open
itpropro opened this issue May 27, 2020 · 4 comments
Open

Use with node.js #55

itpropro opened this issue May 27, 2020 · 4 comments

Comments

@itpropro
Copy link

Hello,
Is there an example on how we could use this in node js? It would be a great functionality for microservice and serverless applications.

@mrin9
Copy link
Owner

mrin9 commented May 27, 2020

this being a web-component based solution, there isn't any good tooling AFAIK that can render web-components on server

That being said keep an eye on this space,
We do have it planned out in our roadmap, but need to wait for lit-ssr to be in some usable form

anther approach would be to factor out the core-engine and create a CLI, it can be done using lit-html-server. I am open for a PR in this direction too

@andyrooger
Copy link
Contributor

If you need this now, it's possible to do with jsdom. Load up the index.html in there, and mock window.URL.createObjectURL in the DOM and you'll get a blob back with the PDF content.

I'm afraid I don't have a code example but this is what I've been using for the past couple of days. It's just a pain to get the blob output to a file afterwards, but things like this should work so long as you're getting globals from the jsdom window.

@Enet4
Copy link

Enet4 commented May 10, 2023

For what it's worth, we were able to use RapiPdf in Node.js with something like this:

const fs = require('fs');
const jsdom = require('jsdom');
const virtualConsole = new jsdom.VirtualConsole();
const {JSDOM} = jsdom;

const script = fs.readFileSync('./node_modules/rapipdf/dist/rapipdf-min.js', 'utf-8');

const json = fs.readFileSync('api.json', 'utf-8');
const obj = JSON.parse(json);
const dom = new JSDOM(`
<!doctype html>
<script>${script}</script>
<rapi-pdf id="thedoc"></rapi-pdf>
`, {
    virtualConsole,
    runScripts: 'dangerously'
});
const {document} = dom.window;
dom.window.onopen = function () {};
dom.window.URL.createObjectURL = (blob) => {
    const reader = new dom.window.FileReader();
    reader.addEventListener('loadend', () => {
        fs.writeFileSync('out.pdf', Buffer.from(reader.result));
    });
    reader.readAsArrayBuffer(blob);
};
document.addEventListener('DOMContentLoaded', async () => {
    const rapiPdf = document.querySelector('rapi-pdf');
    rapiPdf.generatePdf(obj);
});

Unfortunately, it stopped working with Node.js 18. Further inspection revealed that the custom element type rapi-pdf failed to register due to a JavaScript error when loaded by jsdom.

JS DOM error: Error: Uncaught [TypeError: Cannot redefine property: onmessage]
    at reportException (...\docs\node_modules\jsdom\lib\jsdom\living\helpers\runtime-script-errors.js:66:24)
    at processJavaScript (...\docs\node_modules\jsdom\lib\jsdom\living\nodes\HTMLScriptElement-impl.js:240:7)
    at HTMLScriptElementImpl._innerEval (...\docs\node_modules\jsdom\lib\jsdom\living\nodes\HTMLScriptElement-impl.js:173:5)
    at ...\docs\node_modules\jsdom\lib\jsdom\living\nodes\HTMLScriptElement-impl.js:114:12
    at ResourceQueue.push (...\docs\node_modules\jsdom\lib\jsdom\browser\resources\resource-queue.js:53:16)
    at HTMLScriptElementImpl._fetchInternalScript (...\docs\node_modules\jsdom\lib\jsdom\living\nodes\HTMLScriptElement-impl.js:113:21)
    at HTMLScriptElementImpl._eval (...\docs\node_modules\jsdom\lib\jsdom\living\nodes\HTMLScriptElement-impl.js:167:12)
    at HTMLScriptElementImpl._poppedOffStackOfOpenElements (...\docs\node_modules\jsdom\lib\jsdom\living\nodes\HTMLScriptElement-impl.js:130:10)
    at JSDOMParse5Adapter.onItemPop (...\docs\node_modules\jsdom\lib\jsdom\browser\parser\html.js:175:43)
    at Parser.onItemPop (...\docs\node_modules\parse5\dist\cjs\parser\index.js:158:90) {
  detail: TypeError: Cannot redefine property: onmessage
      at about:blank:55:149568
      at about:blank:55:149574
      at Object.<anonymous> (about:blank:55:150731)
      at Object.<anonymous> (about:blank:55:150783)
      at D (about:blank:6:7214)
      at r (about:blank:6:568)
      at Object.<anonymous> (about:blank:55:54348)
      at Object.<anonymous> (about:blank:55:54589)
      at D (about:blank:6:7214)
      at r (about:blank:6:568),
  type: 'unhandled exception'
}
...\docs\use_rapipdf.js:43
    rapiPdf.generatePdf({});
            ^

TypeError: rapiPdf.generatePdf is not a function
    at Document.<anonymous> (...\docs\use_rapipdf.js:43:13)
    at Document.callTheUserObjectsOperation (...\docs\node_modules\jsdom\lib\jsdom\living\generated\EventListener.js:26:30)
    at innerInvokeEventListeners (...\docs\node_modules\jsdom\lib\jsdom\living\events\EventTarget-impl.js:350:25)
    at invokeEventListeners (...\docs\node_modules\jsdom\lib\jsdom\living\events\EventTarget-impl.js:286:3)
    at DocumentImpl._dispatch (...\docs\node_modules\jsdom\lib\jsdom\living\events\EventTarget-impl.js:233:9)
    at fireAnEvent (...\docs\node_modules\jsdom\lib\jsdom\living\helpers\events.js:18:36)
    at dispatchEvent (...\docs\node_modules\jsdom\lib\jsdom\living\nodes\Document-impl.js:452:9)
    at ...\docs\node_modules\jsdom\lib\jsdom\living\nodes\Document-impl.js:457:11
    at new Promise (<anonymous>)
    at onDOMContentLoad (...\docs\node_modules\jsdom\lib\jsdom\living\nodes\Document-impl.js:455:14)

Node.js v18.16.0

There might be a way to fix this particular problem, but I wouldn't know where to look at the moment.

Update: See jsdom/jsdom#3546

@BertEzendam
Copy link

Enet4, Thanks for the example above. I used it and enhanced it. Here's a full node.js pdf document generator which I used on a WSL Ubuntu linux.
`"use strict";

/eslint-disable no-console/

const fs = require("fs");
const path = require("path");
const util = require("util");
const yaml = require("js-yaml");
const jsdom = require("jsdom");
const virtualConsole = new jsdom.VirtualConsole();
const { JSDOM } = jsdom;
const script = fs.readFileSync(path.join(__dirname, "rapipdf-min.js"), "utf-8");
const dom = new JSDOM(
<!doctype html> <script>${script}</script> <rapi-pdf id="thedoc"></rapi-pdf>,
{
virtualConsole,
runScripts: "dangerously",
}
);
const { document } = dom.window;
var verbose = false;
var warn = false;

// REPLACEREFS
// Explanation:
// Function Definition:

// replaceRefs(json) is the main function that takes the JSON object and replaces all $ref references with their corresponding content.
// getRefContent(ref, root) is a helper function that takes a $ref string and the root of the JSON structure. It returns the content at the path specified by the $ref.
//
// Get Reference Content:
// If the $ref starts with #, it is treated as an internal reference, and the function proceeds to traverse the path within the JSON structure.
// If the $ref does not start with #, it is treated as a file path. The function checks the file extension:
// For yaml and yml extensions, it reads and parses the file using yaml.load.
// For json extensions, it reads and parses the file using JSON.parse.
// If the file extension is unsupported, an error is logged.
// The parsed content of the file is then processed with replaceRefs.
// Logging statements print each step of the path traversal and file loading process.
//
// Recursive Traversal:
// If the current object (obj) is an array, it iterates over each element and calls recurse on it, passing the array as the parent and the index as the key.
// If the current object (obj) is a plain object, it iterates over its keys.
// If the key is $ref, it retrieves the new content from the JSON structure or external file using the getRefContent function.
// The retrieved new content replaces the entire object entry (parent[key]), and a deep copy of the new content is created to avoid reference issues.
// The function then recursively scans the new content for further $ref replacements.
// If the key is not $ref, it recursively calls recurse on the value of the key, passing the current object as the parent and the current key.
//
function replaceRefs(json) {
function getRefContent(ref, root) {
if (ref.startsWith("#")) {
// Internal reference
const path = ref.replace(/^#/?/, "").split("/");
if (verbose) console.log(Resolving $ref: ${ref});
let acc = root;
for (const part of path) {
if (acc && acc[part] !== undefined) {
//console.log( Accessing: ${part} ->, acc[part]);
acc = acc[part];
} else {
if (warn || verbose)
console.warn(
Path not found: ${part}, possibly forward referenced
);
return undefined;
}
}
return acc;
} else {
// External file reference
try {
const fileExtension = ref.split(".").pop();
let fileContent;

    if (fileExtension === "yaml" || fileExtension === "yml") {
      fileContent = yaml.load(fs.readFileSync(ref, "utf8"));
      if (verbose) console.log(`Resolving external $ref YAML file: ${ref}`);
    } else if (fileExtension === "json") {
      fileContent = JSON.parse(fs.readFileSync(ref, "utf8"));
      if (verbose) console.log(`Resolving external $ref JSON file: ${ref}`);
    } else {
      console.error(`Unsupported file extension: ${fileExtension}`);
      return undefined;
    }

    fileContent = replaceRefs(fileContent); // pass 1: Resolve file includes and non forward references
    fileContent = replaceRefs(fileContent); // pass 2: Resolve forward references
    return fileContent;
  } catch (error) {
    console.error(`Error reading or parsing file: ${ref}`, error);
    return undefined;
  }
}

}

function recurse(obj, parent, key) {
if (typeof obj === "object" && obj !== null) {
if (Array.isArray(obj)) {
obj.forEach((item, index) => recurse(item, obj, index));
} else {
for (let k in obj) {
if (obj.hasOwnProperty(k)) {
if (k === "$ref") {
const newContent = getRefContent(obj[k], json);
if (newContent !== undefined) {
parent[key] = JSON.parse(JSON.stringify(newContent)); // Deep copy to avoid reference issues
recurse(parent[key], parent, key); // Recurse into the new content
return; // Return early since the current object has been replaced
}
} else {
recurse(obj[k], obj, k);
}
}
}
}
}
}

recurse(json, null, null);
return json;
}

// Main function to handle input and output filenames
function main() {
const args = process.argv.slice(2);
const inputFile = args.find((arg) => !arg.startsWith("-"));
let outputFile =
args.find((arg) => arg.startsWith("-"))?.replace(/^-/, "") || "";
verbose = args.includes("-v");
warn = args.includes("-w");

if (!inputFile) {
console.error("Usage: node gendoc.js [-v] [-w] []");
process.exit(1);
}

if (!outputFile) {
// Default output file to input filename with .pdf extension
const ext = path.extname(inputFile);
const base = path.basename(inputFile, ext);
outputFile = ${base}.pdf;
}

try {
const filename = path.join(__dirname, inputFile);
const fileExtension = path.extname(inputFile).toLowerCase();
const contents = fs.readFileSync(filename, "utf8");
var json;

// Read the input file
if (fileExtension === ".yaml" || fileExtension === ".yml") {
  json = yaml.load(contents);
} else if (fileExtension === ".json") {
  json = JSON.parse(contents);
} else {
  console.error(`Unsupported input file extension: ${fileExtension}`);
  process.exit(1);
}

// Process the JSON content
json = replaceRefs(json); // pass 1: Resolve file includes and non forward references
json = replaceRefs(json); // pass 2: Resolve forward references
if (verbose) console.log(JSON.parse(JSON.stringify(json)));

} catch (error) {
console.error(Error processing file: ${error.message});
process.exit(1);
}
// Generate the PDF output
dom.window.onopen = function () {};
dom.window.URL.createObjectURL = (blob) => {
const reader = new dom.window.FileReader();
reader.addEventListener("loadend", () => {
fs.writeFileSync(outputFile, Buffer.from(reader.result));
});
reader.readAsArrayBuffer(blob);
};
document.addEventListener("DOMContentLoaded", async () => {
const rapiPdf = document.querySelector("rapi-pdf");
rapiPdf.generatePdf(json);
});
}
gendoc.zip

// Execute the main function
main();
`
Maybe someone can add this to a git repository as a tool.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants