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.
Files changed (4) hide show
  1. package/README.md +222 -234
  2. package/index.d.ts +2 -0
  3. package/index.js +53 -16
  4. 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
- ## Your own client
336
+ ## Custom Browser Arguments
311
337
 
312
- 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.
338
+ ### Using `args` export
313
339
 
314
- 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.
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
- Pass `url` option to `createInstance` function with public url of your client editor.
342
+ ```js
343
+ const { args } = require('polotno-node');
317
344
 
318
- **Note: you will have to maintain the last version of your client editor by yourself. Better to keep using the last **
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: 'KEY',
325
- url: 'https://yourappdomain.com/client',
366
+ key: 'your-key',
367
+ browser,
326
368
  });
327
369
  ```
328
370
 
329
- ## Usage on the cloud
371
+ ### Modifying default arguments
330
372
 
331
- ### AWS EC2
373
+ If you need to add or remove specific arguments, you can do so by spreading the array:
332
374
 
333
- EC2 has some troubles with loading fonts. To fix the issue install Google Chrome, it will load all required libraries.
375
+ ```js
376
+ const { createInstance, args } = require('polotno-node');
377
+ const puppeteer = require('puppeteer-core');
334
378
 
335
- ```bash
336
- curl https://intoli.com/install-google-chrome.sh | bash
337
- ```
379
+ // Add custom arguments
380
+ const browser = await puppeteer.launch({
381
+ args: [...args, '--custom-arg', '--another-custom-arg'],
382
+ });
338
383
 
339
- Got it from here: https://github.com/puppeteer/puppeteer/issues/765#issuecomment-353694116
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
- ### Browserless usage
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
- 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.
399
+ ### Combining with `browserArgs` option
344
400
 
345
- 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.
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
- // (!) loading from polotno-node/instance will not import puppeteer and chromium-min dependencies
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: 'nFA5H9elEytDyPyvKL7T',
354
- browser: await puppeteer.connect({
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
- ### Minimal usage
412
+ ## Your own client
362
413
 
363
- 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!
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
- ```bash
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
- ```js
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
- const makeInstance = async () => {
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
- return await createInstance({
396
- key: 'your-key',
397
- browser,
398
- });
399
- };
422
+ ```js
423
+ const { createInstance } = require('polotno-node');
400
424
 
401
- const instance = await makeInstance();
425
+ const instance = await createInstance({
426
+ key: 'KEY',
427
+ url: 'https://yourappdomain.com/client',
428
+ });
402
429
  ```
403
430
 
404
- ## AWS lambda with Layer configuration
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
- - The chronium and puppeteer versions need to be satisfied. Please check this [document](https://pptr.dev/supported-browsers).
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
- How to create a configuration layer with a dependency on `chronium` ?
437
+ ```js
438
+ const { createInstance } = require('polotno-node');
424
439
 
425
- 1. Create a `.zip` file from a chronium project. For example:
440
+ export const handler = async (event) => {
441
+ const instance = await createInstance({
442
+ key: process.env.POLOTNO_API_KEY,
443
+ });
426
444
 
427
- ```shell
428
- mkdir chronium-112 && cd chronium-112
445
+ const base64 = await instance.jsonToImageBase64(event.json);
429
446
 
430
- npm init -y
431
- npm install @sparticuz/chromium@112.0
447
+ await instance.close();
432
448
 
433
- zip -r chronium.zip ./*
449
+ return {
450
+ statusCode: 200,
451
+ headers: {
452
+ 'Content-Type': 'image/png',
453
+ },
454
+ body: base64,
455
+ };
456
+ };
434
457
  ```
435
458
 
436
- 2. Go to AWS console then open a Lambda section and click on `Layers`.
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
- > Approximatelly, the size of the zip, will be around `73 Mb`, so in order to upload, the S3 service will be needed. 4. Finally, open the Lambda function, select a `Code` section, at the bottom click on `Add Layer` and select a created layer.
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
- How to run the full example with aws-cli ?
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
- ### 1. Create a Lambda configuration layer
469
+ #### AWS Lambda with Layers (Optional)
446
470
 
447
- Create a Lambda configuration layer, and upload a **.zip** archive which stores a **chronium** npm or git package.
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
- ```shell
450
- export CH_VERSION=112.0
451
- export BUCKET=example-bucket
452
- export AWS_ACC_ID=000000000000
473
+ **Dependencies:**
453
474
 
454
- # create a chronium project for Layer
455
- mkdir chronium-layer
456
- cd chronium-layer
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
- # upload a Layer to S3
462
- aws s3 cp chronium.zip s3://${BUCKET}/lambda-layers/chronium-${CH_VERSION}.zip
479
+ **Requirements:**
463
480
 
464
- # publish a Layer so it can be used in Lambda
465
- aws lambda publish-layer-version \
466
- --layer-name chronium-polotno \
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
- ### 2. Create a JS file for a Lambda's handler
485
+ **Creating a Lambda Layer with chromium:**
474
486
 
475
- #### Init a node project
487
+ 1. Create a `.zip` file from a chromium project:
476
488
 
477
489
  ```shell
478
- # create a folder
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
- # install dependencies
485
- npm install --save polotno-node puppeteer-core@19.8.0
495
+ zip -r chromium.zip ./*
486
496
  ```
487
497
 
488
- #### Create a handler
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
- ```shell
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
- return {
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
- <details>
532
- <summary>Highlighted code example from the command above:</summary>
505
+ **Handler code with custom chromium:**
506
+
507
+ Create `index.mjs`:
533
508
 
534
509
  ```js
535
- import chromium from "@sparticuz/chromium";
536
- import puppeteer from "puppeteer-core";
537
- import { createInstance } from "polotno-node";
538
- export const handler = () => {
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
- ...chromium.args,
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
- const body = polotnoInstance.jsonToImageBase64(event.json)
528
+
529
+ const body = await polotnoInstance.jsonToImageBase64(event.json);
530
+
531
+ await polotnoInstance.close();
558
532
 
559
533
  return {
560
- StatusCode: 200,
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
- </details>
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
- ### AWS Lambda fonts issue
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
- If you have faced an issue `Timeout for loading font <font name>`, please proceed to the next solution.
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
@@ -89,6 +89,8 @@ declare module 'polotno-node' {
89
89
  }
90
90
 
91
91
  // Main Module Exports
92
+ export const args: string[];
93
+
92
94
  export function createBrowser(options?: BrowserOptions): Promise<Browser>;
93
95
 
94
96
  export function createInstance(
package/index.js CHANGED
@@ -13,8 +13,10 @@ if (isMacOs || isWindows) {
13
13
 
14
14
  const { createInstance } = require('./instance');
15
15
 
16
- const minimal_args = [
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
- '--no-proxy-server',
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
- '--disable-font-subpixel-positioning',
74
- '--force-color-profile=generic-rgb',
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:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polotno-node",
3
- "version": "2.12.29",
3
+ "version": "2.12.30",
4
4
  "description": "Polotno workflow from NodeJS",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",