polotno-node 2.12.29 → 2.12.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +222 -234
- package/index.d.ts +2 -0
- package/index.js +53 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -67,6 +67,10 @@ const instance = await createInstance({
|
|
|
67
67
|
// useful to set your own rendering props or use browserless
|
|
68
68
|
browser: browser,
|
|
69
69
|
|
|
70
|
+
// browserArgs - additional browser arguments to append to default args
|
|
71
|
+
// see "Custom Browser Arguments" section for more details
|
|
72
|
+
browserArgs: ['--custom-arg'],
|
|
73
|
+
|
|
70
74
|
// requestInterceptor - optional function to intercept and modify network requests
|
|
71
75
|
// Useful when you need to:
|
|
72
76
|
// - Modify headers like User-Agent to access protected image resources
|
|
@@ -89,6 +93,28 @@ const instance = await createInstance({
|
|
|
89
93
|
});
|
|
90
94
|
```
|
|
91
95
|
|
|
96
|
+
### `createBrowser(options)`
|
|
97
|
+
|
|
98
|
+
Create a Puppeteer browser instance with optimized settings for Polotno rendering. This is useful when you want to create a browser separately from the instance.
|
|
99
|
+
|
|
100
|
+
```js
|
|
101
|
+
const { createBrowser, createInstance } = require('polotno-node');
|
|
102
|
+
|
|
103
|
+
// Create a browser
|
|
104
|
+
const browser = await createBrowser({
|
|
105
|
+
browserArgs: ['--custom-arg'], // optional: additional browser arguments
|
|
106
|
+
// ... any other puppeteer.launch options
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Create instance with the browser
|
|
110
|
+
const instance = await createInstance({
|
|
111
|
+
key: 'your-key',
|
|
112
|
+
browser: browser,
|
|
113
|
+
});
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**Note:** `createBrowser()` automatically uses the optimized `args` for rendering. You can add custom arguments via the `browserArgs` parameter.
|
|
117
|
+
|
|
92
118
|
### `instance.jsonToDataURL(json, attrs)`
|
|
93
119
|
|
|
94
120
|
Export json into data URL.
|
|
@@ -307,325 +333,219 @@ const url = await instance.run(async (json) => {
|
|
|
307
333
|
}, json);
|
|
308
334
|
```
|
|
309
335
|
|
|
310
|
-
##
|
|
336
|
+
## Custom Browser Arguments
|
|
311
337
|
|
|
312
|
-
|
|
338
|
+
### Using `args` export
|
|
313
339
|
|
|
314
|
-
|
|
340
|
+
`polotno-node` exports a carefully curated set of Chrome arguments (`args`) that are optimized for server-side rendering. These arguments are automatically used when you call `createInstance()` without providing your own browser.
|
|
315
341
|
|
|
316
|
-
|
|
342
|
+
```js
|
|
343
|
+
const { args } = require('polotno-node');
|
|
317
344
|
|
|
318
|
-
|
|
345
|
+
console.log(args);
|
|
346
|
+
// Will show the default arguments like:
|
|
347
|
+
// ['--disable-web-security', '--allow-file-access-from-files', '--disable-gpu', ...]
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
### Using with custom browser
|
|
351
|
+
|
|
352
|
+
When you want to use your own browser instance (e.g., with browserless.io or custom puppeteer configuration), you can still use these default arguments:
|
|
319
353
|
|
|
320
354
|
```js
|
|
321
|
-
const { createInstance } = require('polotno-node');
|
|
355
|
+
const { createInstance, args } = require('polotno-node');
|
|
356
|
+
const puppeteer = require('puppeteer-core');
|
|
357
|
+
|
|
358
|
+
// Use polotno-node's default args with your custom browser
|
|
359
|
+
const browser = await puppeteer.launch({
|
|
360
|
+
args: args,
|
|
361
|
+
headless: true,
|
|
362
|
+
// ... other browser options
|
|
363
|
+
});
|
|
322
364
|
|
|
323
365
|
const instance = await createInstance({
|
|
324
|
-
key: '
|
|
325
|
-
|
|
366
|
+
key: 'your-key',
|
|
367
|
+
browser,
|
|
326
368
|
});
|
|
327
369
|
```
|
|
328
370
|
|
|
329
|
-
|
|
371
|
+
### Modifying default arguments
|
|
330
372
|
|
|
331
|
-
|
|
373
|
+
If you need to add or remove specific arguments, you can do so by spreading the array:
|
|
332
374
|
|
|
333
|
-
|
|
375
|
+
```js
|
|
376
|
+
const { createInstance, args } = require('polotno-node');
|
|
377
|
+
const puppeteer = require('puppeteer-core');
|
|
334
378
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
379
|
+
// Add custom arguments
|
|
380
|
+
const browser = await puppeteer.launch({
|
|
381
|
+
args: [...args, '--custom-arg', '--another-custom-arg'],
|
|
382
|
+
});
|
|
338
383
|
|
|
339
|
-
|
|
384
|
+
// Remove specific arguments
|
|
385
|
+
const filteredArgs = args.filter((arg) => arg !== '--disable-gpu');
|
|
386
|
+
const browser2 = await puppeteer.launch({
|
|
387
|
+
args: filteredArgs,
|
|
388
|
+
});
|
|
340
389
|
|
|
341
|
-
|
|
390
|
+
// Replace specific arguments
|
|
391
|
+
const customArgs = args.map((arg) =>
|
|
392
|
+
arg === '--disable-web-security' ? '--enable-web-security' : arg
|
|
393
|
+
);
|
|
394
|
+
const browser3 = await puppeteer.launch({
|
|
395
|
+
args: customArgs,
|
|
396
|
+
});
|
|
397
|
+
```
|
|
342
398
|
|
|
343
|
-
|
|
399
|
+
### Combining with `browserArgs` option
|
|
344
400
|
|
|
345
|
-
|
|
401
|
+
When using `createInstance()` or `createBrowser()`, you can provide additional arguments via `browserArgs` option. These will be appended to the default arguments:
|
|
346
402
|
|
|
347
403
|
```js
|
|
348
|
-
|
|
349
|
-
const { createInstance } = require('polotno-node/instance');
|
|
350
|
-
const puppeteer = require('puppeteer');
|
|
404
|
+
const { createInstance } = require('polotno-node');
|
|
351
405
|
|
|
352
406
|
const instance = await createInstance({
|
|
353
|
-
key: '
|
|
354
|
-
|
|
355
|
-
browserWSEndpoint: 'wss://chrome.browserless.io?token=API_KEY',
|
|
356
|
-
}),
|
|
357
|
-
url: 'https://yourappdomain.com/client', // see "Your own client" section
|
|
407
|
+
key: 'your-key',
|
|
408
|
+
browserArgs: ['--custom-arg', '--another-arg'],
|
|
358
409
|
});
|
|
359
410
|
```
|
|
360
411
|
|
|
361
|
-
|
|
412
|
+
## Your own client
|
|
362
413
|
|
|
363
|
-
|
|
414
|
+
By default `polotno-node` ships with the default Polotno Editor with its (hopefully) last version. If you use experimental API such as `unstable_registerShapeModel` and `unstable_registerShapeComponent`, the rendering may fail if you use unknown elements types.
|
|
364
415
|
|
|
365
|
-
|
|
366
|
-
npm install @sparticuz/chromium-min
|
|
367
|
-
```
|
|
416
|
+
In that case you can use your own client editor. You need to create a public html page with `store` as global variable and mount just `<Workspace />` component from `polotno/canvas` module. Take a look into `client.html` file and `client.js` file in this repo as a demo. In your own version of the Editor you can use experimental API to define custom components.
|
|
368
417
|
|
|
369
|
-
|
|
370
|
-
const { createInstance } = require('polotno-node/instance');
|
|
371
|
-
const chromium = require('@sparticuz/chromium-min');
|
|
372
|
-
const puppeteer = require('puppeteer-core');
|
|
418
|
+
Pass `url` option to `createInstance` function with public url of your client editor.
|
|
373
419
|
|
|
374
|
-
|
|
375
|
-
const browser = await puppeteer.launch({
|
|
376
|
-
args: [
|
|
377
|
-
...chromium.args,
|
|
378
|
-
'--no-sandbox',
|
|
379
|
-
'--hide-scrollbars',
|
|
380
|
-
'--disable-web-security',
|
|
381
|
-
'--allow-file-access-from-files',
|
|
382
|
-
// more info about --disable-dev-shm-usage
|
|
383
|
-
// https://github.com/puppeteer/puppeteer/issues/1175#issuecomment-369728215
|
|
384
|
-
// https://github.com/puppeteer/puppeteer/blob/main/docs/troubleshooting.md#tips
|
|
385
|
-
'--disable-dev-shm-usage',
|
|
386
|
-
],
|
|
387
|
-
defaultViewport: chromium.defaultViewport,
|
|
388
|
-
executablePath: await chromium.executablePath(
|
|
389
|
-
'https://github.com/Sparticuz/chromium/releases/download/v110.0.1/chromium-v110.0.1-pack.tar'
|
|
390
|
-
),
|
|
391
|
-
headless: chromium.headless,
|
|
392
|
-
ignoreHTTPSErrors: true,
|
|
393
|
-
});
|
|
420
|
+
**Note: you will have to maintain the last version of your client editor by yourself. Better to keep using the last **
|
|
394
421
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
browser,
|
|
398
|
-
});
|
|
399
|
-
};
|
|
422
|
+
```js
|
|
423
|
+
const { createInstance } = require('polotno-node');
|
|
400
424
|
|
|
401
|
-
const instance = await
|
|
425
|
+
const instance = await createInstance({
|
|
426
|
+
key: 'KEY',
|
|
427
|
+
url: 'https://yourappdomain.com/client',
|
|
428
|
+
});
|
|
402
429
|
```
|
|
403
430
|
|
|
404
|
-
##
|
|
405
|
-
|
|
406
|
-
In order to run the **polotno-node** in AWS Lambda, it's recommended to use a [Lambda Layer configurations](https://docs.aws.amazon.com/lambda/latest/dg/chapter-layers.html) which will manage the dependencies, like **chronium**.
|
|
407
|
-
In addition, the Lambda's configuration needs to be changed as well. It's recommeded to change the **timeout** to 30 seconds instead of default value (3 seconds) and **Memory limit** increase to minimum 512 Mb instead of 128 mb ( default value ).
|
|
408
|
-
|
|
409
|
-
Dependencies:
|
|
410
|
-
|
|
411
|
-
- @sparticuz/chromium
|
|
412
|
-
- puppeteer-core
|
|
413
|
-
- polotno-node
|
|
414
|
-
|
|
415
|
-
Pre-requirements:
|
|
431
|
+
## Usage on the cloud
|
|
416
432
|
|
|
417
|
-
|
|
418
|
-
- The **minumum Memory limit** needs to be `512 Mb` for AWS Lambda. Default is `128 Mb`.
|
|
419
|
-
- The **timeout** should be increased from default 3 seconds to higher value, for example up to 30 seconds. It depends on the Memory limit. For instance, if the memory limit was set to 1024, the polotno process might take ~15 secs, if 512 mb, ~30sec.
|
|
433
|
+
### AWS Lambda
|
|
420
434
|
|
|
421
|
-
|
|
435
|
+
`polotno-node` works with AWS Lambda out of the box. Here's a simple example:
|
|
422
436
|
|
|
423
|
-
|
|
437
|
+
```js
|
|
438
|
+
const { createInstance } = require('polotno-node');
|
|
424
439
|
|
|
425
|
-
|
|
440
|
+
export const handler = async (event) => {
|
|
441
|
+
const instance = await createInstance({
|
|
442
|
+
key: process.env.POLOTNO_API_KEY,
|
|
443
|
+
});
|
|
426
444
|
|
|
427
|
-
|
|
428
|
-
mkdir chronium-112 && cd chronium-112
|
|
445
|
+
const base64 = await instance.jsonToImageBase64(event.json);
|
|
429
446
|
|
|
430
|
-
|
|
431
|
-
npm install @sparticuz/chromium@112.0
|
|
447
|
+
await instance.close();
|
|
432
448
|
|
|
433
|
-
|
|
449
|
+
return {
|
|
450
|
+
statusCode: 200,
|
|
451
|
+
headers: {
|
|
452
|
+
'Content-Type': 'image/png',
|
|
453
|
+
},
|
|
454
|
+
body: base64,
|
|
455
|
+
};
|
|
456
|
+
};
|
|
434
457
|
```
|
|
435
458
|
|
|
436
|
-
|
|
437
|
-
3. Following the [documentation](https://docs.aws.amazon.com/lambda/latest/dg/chapter-layers.html) create a Layer with a chrnioum dependency by uploading a zip file. Keep in mind that environment like `nodejs18.x` should match between layer and function.
|
|
459
|
+
**Important:** For reliable performance, you may need to increase AWS Lambda limits:
|
|
438
460
|
|
|
439
|
-
|
|
461
|
+
- **Memory**: Increase from the default. For complex designs, you may need to set it to maximum.
|
|
462
|
+
- **Timeout**: Increase from the default. For large files, you may need to set it to maximum.
|
|
463
|
+
- **Ephemeral Storage**: May need to increase from the default for complex designs.
|
|
440
464
|
|
|
441
|
-
|
|
465
|
+
Without these increases, polotno-node may work on smaller files but will fail or timeout on larger files.
|
|
442
466
|
|
|
443
|
-
|
|
467
|
+
**Full working example:** See [polotno-node-aws-lambda](https://github.com/polotno-project/polotno-node-aws-lambda) for a complete demo.
|
|
444
468
|
|
|
445
|
-
|
|
469
|
+
#### AWS Lambda with Layers (Optional)
|
|
446
470
|
|
|
447
|
-
|
|
471
|
+
For advanced usage, you can use [Lambda Layers](https://docs.aws.amazon.com/lambda/latest/dg/chapter-layers.html) to manage dependencies like chromium separately. This can help with deployment size and organization.
|
|
448
472
|
|
|
449
|
-
|
|
450
|
-
export CH_VERSION=112.0
|
|
451
|
-
export BUCKET=example-bucket
|
|
452
|
-
export AWS_ACC_ID=000000000000
|
|
473
|
+
**Dependencies:**
|
|
453
474
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
npm init -y
|
|
458
|
-
npm install @sparticuz/chromium@${CH_VERSION}
|
|
459
|
-
zip -r chronium.zip ./*
|
|
475
|
+
- @sparticuz/chromium
|
|
476
|
+
- puppeteer-core
|
|
477
|
+
- polotno-node
|
|
460
478
|
|
|
461
|
-
|
|
462
|
-
aws s3 cp chronium.zip s3://${BUCKET}/lambda-layers/chronium-${CH_VERSION}.zip
|
|
479
|
+
**Requirements:**
|
|
463
480
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
--content "S3Bucket=${BUCKET},S3Key=lambda-layers/chronium-{CH_VERSION}.zip" \
|
|
468
|
-
--description Chronium-dev-${CH_VERSION} \
|
|
469
|
-
--compatible-architectures x86_64 \
|
|
470
|
-
--compatible-runtimes nodejs18.x
|
|
471
|
-
```
|
|
481
|
+
- The chromium and puppeteer versions need to be compatible. Please check this [document](https://pptr.dev/supported-browsers).
|
|
482
|
+
- The **Memory limit** needs to be increased from the default. You may need to set it to maximum for complex designs.
|
|
483
|
+
- The **timeout** should be increased from the default. You may need to set it to maximum for large files.
|
|
472
484
|
|
|
473
|
-
|
|
485
|
+
**Creating a Lambda Layer with chromium:**
|
|
474
486
|
|
|
475
|
-
|
|
487
|
+
1. Create a `.zip` file from a chromium project:
|
|
476
488
|
|
|
477
489
|
```shell
|
|
478
|
-
|
|
479
|
-
mkdir handler && cd handler
|
|
490
|
+
mkdir chromium-112 && cd chromium-112
|
|
480
491
|
|
|
481
|
-
# init a node project
|
|
482
492
|
npm init -y
|
|
493
|
+
npm install @sparticuz/chromium@112.0
|
|
483
494
|
|
|
484
|
-
|
|
485
|
-
npm install --save polotno-node puppeteer-core@19.8.0
|
|
495
|
+
zip -r chromium.zip ./*
|
|
486
496
|
```
|
|
487
497
|
|
|
488
|
-
|
|
498
|
+
2. Go to AWS console then open Lambda section and click on `Layers`.
|
|
499
|
+
3. Following the [documentation](https://docs.aws.amazon.com/lambda/latest/dg/chapter-layers.html) create a Layer with a chromium dependency by uploading a zip file. Keep in mind that environment like `nodejs18.x` should match between layer and function.
|
|
489
500
|
|
|
490
|
-
|
|
491
|
-
# create a file handler
|
|
492
|
-
touch index.mjs
|
|
493
|
-
|
|
494
|
-
# add code example
|
|
495
|
-
echo "
|
|
496
|
-
import chromium from "@sparticuz/chromium";
|
|
497
|
-
import puppeteer from "puppeteer-core";
|
|
498
|
-
import { createInstance } from "polotno-node";
|
|
499
|
-
export const handler = () => {
|
|
500
|
-
const browser = await puppeteer.launch({
|
|
501
|
-
args: [
|
|
502
|
-
...chromium.args,
|
|
503
|
-
"--no-sandbox",
|
|
504
|
-
"--hide-scrollbars",
|
|
505
|
-
"--disable-web-security",
|
|
506
|
-
"--allow-file-access-from-files",
|
|
507
|
-
"--disable-dev-shm-usage",
|
|
508
|
-
],
|
|
509
|
-
defaultViewport: chromium.defaultViewport,
|
|
510
|
-
executablePath: await chromium.executablePath(),
|
|
511
|
-
headless: true,
|
|
512
|
-
ignoreHTTPSErrors: true,
|
|
513
|
-
});
|
|
514
|
-
const polotnoInstance = await createInstance({
|
|
515
|
-
key: process.env.POLOTNO_API_KEY,
|
|
516
|
-
browser,
|
|
517
|
-
});
|
|
518
|
-
const body = polotnoInstance.jsonToImageBase64(event.json)
|
|
501
|
+
> The size of the zip will be large, so you may need to use S3 to upload it.
|
|
519
502
|
|
|
520
|
-
|
|
521
|
-
StatusCode: 200,
|
|
522
|
-
headers: {
|
|
523
|
-
'Content-Type': 'image/png'
|
|
524
|
-
},
|
|
525
|
-
body,
|
|
526
|
-
}
|
|
527
|
-
};
|
|
528
|
-
" >> index.mjs
|
|
529
|
-
```
|
|
503
|
+
4. Finally, open the Lambda function, select a `Code` section, at the bottom click on `Add Layer` and select a created layer.
|
|
530
504
|
|
|
531
|
-
|
|
532
|
-
|
|
505
|
+
**Handler code with custom chromium:**
|
|
506
|
+
|
|
507
|
+
Create `index.mjs`:
|
|
533
508
|
|
|
534
509
|
```js
|
|
535
|
-
import chromium from
|
|
536
|
-
import puppeteer from
|
|
537
|
-
import { createInstance } from
|
|
538
|
-
|
|
510
|
+
import chromium from '@sparticuz/chromium';
|
|
511
|
+
import puppeteer from 'puppeteer-core';
|
|
512
|
+
import { createInstance, args } from 'polotno-node';
|
|
513
|
+
|
|
514
|
+
export const handler = async (event) => {
|
|
539
515
|
const browser = await puppeteer.launch({
|
|
540
|
-
args
|
|
541
|
-
|
|
542
|
-
"--no-sandbox",
|
|
543
|
-
"--hide-scrollbars",
|
|
544
|
-
"--disable-web-security",
|
|
545
|
-
"--allow-file-access-from-files",
|
|
546
|
-
"--disable-dev-shm-usage",
|
|
547
|
-
],
|
|
516
|
+
// Combine chromium args with polotno-node's optimized args
|
|
517
|
+
args: [...chromium.args, ...args],
|
|
548
518
|
defaultViewport: chromium.defaultViewport,
|
|
549
519
|
executablePath: await chromium.executablePath(),
|
|
550
520
|
headless: true,
|
|
551
521
|
ignoreHTTPSErrors: true,
|
|
552
522
|
});
|
|
523
|
+
|
|
553
524
|
const polotnoInstance = await createInstance({
|
|
554
525
|
key: process.env.POLOTNO_API_KEY,
|
|
555
526
|
browser,
|
|
556
527
|
});
|
|
557
|
-
|
|
528
|
+
|
|
529
|
+
const body = await polotnoInstance.jsonToImageBase64(event.json);
|
|
530
|
+
|
|
531
|
+
await polotnoInstance.close();
|
|
558
532
|
|
|
559
533
|
return {
|
|
560
|
-
|
|
534
|
+
statusCode: 200,
|
|
561
535
|
headers: {
|
|
562
|
-
'Content-Type': 'image/png'
|
|
536
|
+
'Content-Type': 'image/png',
|
|
563
537
|
},
|
|
564
538
|
body,
|
|
565
|
-
}
|
|
539
|
+
};
|
|
566
540
|
};
|
|
567
541
|
```
|
|
568
542
|
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
#### Create file with variables
|
|
572
|
-
|
|
573
|
-
```shell
|
|
574
|
-
echo '{
|
|
575
|
-
"Variables": {
|
|
576
|
-
"POLOTNO_API_KEY": "your-api-key"
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
' >> vars.json
|
|
580
|
-
|
|
581
|
-
# archive the folder
|
|
582
|
-
zip -r index.zip ./*
|
|
583
|
-
|
|
584
|
-
# copy to S3
|
|
585
|
-
aws s3 cp index.zip s3://${BUCKET}/polotno-handler.zip
|
|
586
|
-
```
|
|
587
|
-
|
|
588
|
-
#### Crete a trust policy
|
|
589
|
-
|
|
590
|
-
```shell
|
|
591
|
-
echo '{
|
|
592
|
-
"Version": "2012-10-17",
|
|
593
|
-
"Statement": [
|
|
594
|
-
{
|
|
595
|
-
"Effect": "Allow",
|
|
596
|
-
"Principal": {
|
|
597
|
-
"Service": "lambda.amazonaws.com"
|
|
598
|
-
},
|
|
599
|
-
"Action": "sts:AssumeRole"
|
|
600
|
-
}
|
|
601
|
-
]
|
|
602
|
-
}
|
|
603
|
-
' >> trustPolicy.json
|
|
604
|
-
```
|
|
605
|
-
|
|
606
|
-
#### Create roles and policies in AWS
|
|
607
|
-
|
|
608
|
-
```shell
|
|
609
|
-
aws iam create-role --role-name lambda-polotno-role --assume-role-policy-document file://trustPolicy.json
|
|
610
|
-
aws iam attach-role-policy --role-name lambda-polotno-role --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
|
|
611
|
-
aws lambda create-function --function-name lambda-polotno \
|
|
612
|
-
--runtime nodejs18.x \
|
|
613
|
-
--zip-file fileb://index.zip \
|
|
614
|
-
--layers "arn:aws:lambda:eu-central-1:${AWS_ACC_ID}:layer:chronium-polotno:1" \
|
|
615
|
-
--handler index.handler \
|
|
616
|
-
--environment file://vars.json \
|
|
617
|
-
--role "arn:aws:iam::${AWS_ACC_ID}:role/lambda-polotno-role" \
|
|
618
|
-
--timeout 60 \
|
|
619
|
-
--memory-size 512
|
|
620
|
-
// try it
|
|
621
|
-
aws lambda invoke --function-name lambda-polotno --cli-binary-format raw-in-base64-out --payload file://polotno-project-json.json image
|
|
622
|
-
```
|
|
543
|
+
#### AWS Lambda fonts issue
|
|
623
544
|
|
|
624
|
-
|
|
545
|
+
Lambda functions do not include any fonts by default. If you encounter `Timeout for loading font <font name>` errors, you need to provide basic fonts (Arial and Times or their analogs).
|
|
625
546
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
Lambda functions does not include any fonts by default, so we need to provide basic fonts (Arial and Times or their analogs) to make sure Polotno fonts functionality is working correctly.
|
|
547
|
+
<details>
|
|
548
|
+
<summary>Solution: Add fonts to your Lambda function</summary>
|
|
629
549
|
|
|
630
550
|
1. Create a `fonts` folder in the root of your handler project.
|
|
631
551
|
|
|
@@ -662,6 +582,74 @@ mkdir fonts
|
|
|
662
582
|
|
|
663
583
|
4. Upload your Lambda function as usual, fonts will be loaded automatically.
|
|
664
584
|
|
|
585
|
+
</details>
|
|
586
|
+
|
|
587
|
+
### AWS EC2
|
|
588
|
+
|
|
589
|
+
EC2 has some troubles with loading fonts. To fix the issue install Google Chrome, it will load all required libraries.
|
|
590
|
+
|
|
591
|
+
```bash
|
|
592
|
+
curl https://intoli.com/install-google-chrome.sh | bash
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
Got it from here: https://github.com/puppeteer/puppeteer/issues/765#issuecomment-353694116
|
|
596
|
+
|
|
597
|
+
### Browserless usage
|
|
598
|
+
|
|
599
|
+
You can speed up your function execution a lot, if instead of using full browser you will use [browserless.io](https://browserless.io/) service. It is a paid service not affiliated with Polotno.
|
|
600
|
+
|
|
601
|
+
Using browserless.io you can also make your function much smaller in size, so it will be possible to deploy to cloud provider with smaller limits, like Vercel.
|
|
602
|
+
|
|
603
|
+
```js
|
|
604
|
+
// (!) loading from polotno-node/instance will not import puppeteer and chromium-min dependencies
|
|
605
|
+
const { createInstance } = require('polotno-node/instance');
|
|
606
|
+
const puppeteer = require('puppeteer');
|
|
607
|
+
|
|
608
|
+
const instance = await createInstance({
|
|
609
|
+
key: 'nFA5H9elEytDyPyvKL7T',
|
|
610
|
+
browser: await puppeteer.connect({
|
|
611
|
+
browserWSEndpoint: 'wss://chrome.browserless.io?token=API_KEY',
|
|
612
|
+
}),
|
|
613
|
+
url: 'https://yourappdomain.com/client', // see "Your own client" section
|
|
614
|
+
});
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
### Minimal usage
|
|
618
|
+
|
|
619
|
+
Also you can use [@sparticuz/chromium-min](https://github.com/Sparticuz/chromium#-min-package) to reduce function size. Make sure it is caching chromium binary in your cloud provider. Looks like Vercel is NOT doing that!
|
|
620
|
+
|
|
621
|
+
```bash
|
|
622
|
+
npm install @sparticuz/chromium-min
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
```js
|
|
626
|
+
const { createInstance } = require('polotno-node/instance');
|
|
627
|
+
// Import args from main entry point for optimal browser configuration
|
|
628
|
+
const { args } = require('polotno-node');
|
|
629
|
+
const chromium = require('@sparticuz/chromium-min');
|
|
630
|
+
const puppeteer = require('puppeteer-core');
|
|
631
|
+
|
|
632
|
+
const makeInstance = async () => {
|
|
633
|
+
const browser = await puppeteer.launch({
|
|
634
|
+
// Combine chromium args with polotno-node's optimized args
|
|
635
|
+
args: [...chromium.args, ...args],
|
|
636
|
+
defaultViewport: chromium.defaultViewport,
|
|
637
|
+
executablePath: await chromium.executablePath(
|
|
638
|
+
'https://github.com/Sparticuz/chromium/releases/download/v110.0.1/chromium-v110.0.1-pack.tar'
|
|
639
|
+
),
|
|
640
|
+
headless: chromium.headless,
|
|
641
|
+
ignoreHTTPSErrors: true,
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
return await createInstance({
|
|
645
|
+
key: 'your-key',
|
|
646
|
+
browser,
|
|
647
|
+
});
|
|
648
|
+
};
|
|
649
|
+
|
|
650
|
+
const instance = await makeInstance();
|
|
651
|
+
```
|
|
652
|
+
|
|
665
653
|
## Troubleshooting
|
|
666
654
|
|
|
667
655
|
If you have an error like this
|
package/index.d.ts
CHANGED
package/index.js
CHANGED
|
@@ -13,8 +13,10 @@ if (isMacOs || isWindows) {
|
|
|
13
13
|
|
|
14
14
|
const { createInstance } = require('./instance');
|
|
15
15
|
|
|
16
|
-
const
|
|
16
|
+
const args = [
|
|
17
17
|
'about:blank',
|
|
18
|
+
'--disable-web-security',
|
|
19
|
+
'--allow-file-access-from-files',
|
|
18
20
|
'--allow-pre-commit-input',
|
|
19
21
|
'--autoplay-policy=user-gesture-required',
|
|
20
22
|
'--disable-background-networking',
|
|
@@ -26,13 +28,11 @@ const minimal_args = [
|
|
|
26
28
|
'--disable-component-update',
|
|
27
29
|
'--disable-default-apps',
|
|
28
30
|
'--disable-dev-shm-usage',
|
|
29
|
-
'--
|
|
30
|
-
"--proxy-server='direct://'",
|
|
31
|
+
'--proxy-server=direct://',
|
|
31
32
|
'--proxy-bypass-list=*',
|
|
32
33
|
'--disable-domain-reliability',
|
|
33
34
|
'--disable-features=AudioServiceOutOfProcess,IsolateOrigins,site-per-process,Translate,BackForwardCache,AvoidUnnecessaryBeforeUnloadCheckSync,IntensiveWakeUpThrottling',
|
|
34
35
|
'--disable-extensions',
|
|
35
|
-
'--disable-features=AudioServiceOutOfProcess',
|
|
36
36
|
'--disable-hang-monitor',
|
|
37
37
|
'--disable-ipc-flooding-protection',
|
|
38
38
|
'--disable-notifications',
|
|
@@ -45,9 +45,9 @@ const minimal_args = [
|
|
|
45
45
|
'--disable-setuid-sandbox',
|
|
46
46
|
'--disable-speech-api',
|
|
47
47
|
'--disable-sync',
|
|
48
|
+
// Use srgb for identical output across Linux/macOS/Windows and to match how CSS colors/images are defined/assumed on the web.
|
|
48
49
|
'--force-color-profile=srgb',
|
|
49
50
|
'--hide-scrollbars',
|
|
50
|
-
'--ignore-gpu-blacklist',
|
|
51
51
|
'--metrics-recording-only',
|
|
52
52
|
'--mute-audio',
|
|
53
53
|
'--no-default-browser-check',
|
|
@@ -58,9 +58,11 @@ const minimal_args = [
|
|
|
58
58
|
'--no-zygote',
|
|
59
59
|
'--password-store=basic',
|
|
60
60
|
'--allow-running-insecure-content',
|
|
61
|
-
// do we need that?
|
|
62
|
-
// '--single-process',
|
|
63
61
|
|
|
62
|
+
// Disable accessibility features to reduce resource usage and improve performance.
|
|
63
|
+
'--disable-renderer-accessibility',
|
|
64
|
+
// stable locale → font fallback/hyphenation
|
|
65
|
+
'--lang=en-US',
|
|
64
66
|
// this line breaks the rendering on most of OS
|
|
65
67
|
// it doesn't allow creating of pages
|
|
66
68
|
//'--use-gl=swiftshader',
|
|
@@ -70,11 +72,52 @@ const minimal_args = [
|
|
|
70
72
|
'--intensive-wake-up-throttling-policy=0',
|
|
71
73
|
'--font-render-hinting=none',
|
|
72
74
|
'--ignore-gpu-blocklist',
|
|
73
|
-
'
|
|
74
|
-
|
|
75
|
+
// don't use this flag, because it makes renderign less accurate
|
|
76
|
+
// see https://community.polotno.com/c/ask/the-ability-to-exclude-args-from-minimal_args-in-polotno-node
|
|
77
|
+
// '--disable-font-subpixel-positioning',
|
|
75
78
|
'--text-rendering=geometricprecision',
|
|
76
79
|
].filter(Boolean);
|
|
77
80
|
|
|
81
|
+
module.exports.args = args;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Merges browser arguments intelligently, handling duplicates and conflicts.
|
|
85
|
+
* Later arguments take precedence over earlier ones.
|
|
86
|
+
* @param {...string[]} argArrays - Arrays of browser arguments to merge
|
|
87
|
+
* @returns {string[]} Merged array of arguments
|
|
88
|
+
*/
|
|
89
|
+
function mergeArgs(...argArrays) {
|
|
90
|
+
const argMap = new Map();
|
|
91
|
+
const warnings = [];
|
|
92
|
+
|
|
93
|
+
for (const argArray of argArrays) {
|
|
94
|
+
for (const arg of argArray) {
|
|
95
|
+
// Extract the flag name (before '=' if present)
|
|
96
|
+
const flagMatch = arg.match(/^(--[^=]+)/);
|
|
97
|
+
const flagName = flagMatch ? flagMatch[1] : arg;
|
|
98
|
+
|
|
99
|
+
if (argMap.has(flagName)) {
|
|
100
|
+
const existingArg = argMap.get(flagName);
|
|
101
|
+
if (existingArg !== arg) {
|
|
102
|
+
warnings.push(
|
|
103
|
+
`Duplicate/conflicting flag detected: "${flagName}". Using: "${arg}" (overriding: "${existingArg}")`
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
argMap.set(flagName, arg);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Log warnings if any duplicates/conflicts were found
|
|
113
|
+
if (warnings.length > 0) {
|
|
114
|
+
// console.warn('Browser arguments merged with conflicts:');
|
|
115
|
+
// warnings.forEach((warning) => console.warn(` - ${warning}`));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return Array.from(argMap.values());
|
|
119
|
+
}
|
|
120
|
+
|
|
78
121
|
const browserProps = {
|
|
79
122
|
headless: 'new',
|
|
80
123
|
// make large timeout, because some renders may take a lot of time
|
|
@@ -85,13 +128,7 @@ const browserProps = {
|
|
|
85
128
|
|
|
86
129
|
async function createBrowser({ browserArgs = [], ...rest } = {}) {
|
|
87
130
|
return puppeteer.launch({
|
|
88
|
-
args: [
|
|
89
|
-
...(isWindows ? [] : chrome.args),
|
|
90
|
-
...minimal_args,
|
|
91
|
-
'--disable-web-security',
|
|
92
|
-
'--allow-file-access-from-files',
|
|
93
|
-
...browserArgs,
|
|
94
|
-
],
|
|
131
|
+
args: mergeArgs(isWindows ? [] : chrome.args, args, browserArgs),
|
|
95
132
|
defaultViewport: chrome.defaultViewport,
|
|
96
133
|
|
|
97
134
|
executablePath:
|