21 Commits

Author SHA1 Message Date
c1ae430976 Add docker-compose.yml 2025-10-07 10:04:12 +00:00
Pierre Ossman
44b7489e73 Merge branch 'switch-to-es-module-publish-and-use-exports' of github.com:elikoga/noVNC 2025-09-15 15:47:58 +02:00
Eli Kogan-Wang
fe29dc6509 Convert NPM bundle to ES-Module format 2025-09-15 15:37:37 +02:00
Eli Kogan-Wang
6cf02042de Remove deprecated "directories" entry 2025-09-15 15:35:18 +02:00
Pierre Ossman
356eab4f4d Scan all buffered data looking for JPEG end
This is much more efficient than looking at two bytes at a time.
2025-09-10 10:21:29 +02:00
Pierre Ossman
d5b18a84ab Expose length of buffered WebSocket data
Some encodings don't know how much data they need, rather they must
probe the data stream until they find an end marker. Expose how much
data is buffered in order to make this search efficient.
2025-09-10 10:20:20 +02:00
Pierre Ossman
23b7219a5d Drop Image data once rendered
Helps the browser to free up the memory right away, rather than waiting
until some later cleanup process. At least Firefox can start consuming
gigabytes of memory without this.
2025-09-10 09:58:09 +02:00
Alexander Zeijlon
8ebd9ddef9 Fix broken Chai import
Chai v6.0.0 introduced a breaking change where file imports now need to
point at 'chai/index.js'. See the corresponding release note.
2025-09-08 16:47:31 +02:00
Alexander Zeijlon
d49d2b366a Use Croatian translations 2025-09-05 10:31:54 +02:00
Milo Ivir
e4def7f715 Add Croatian translation 2025-08-25 18:36:38 +02:00
Alexander Zeijlon
4cb5aa45ae Merge branch 'obsolete-showDotCursor-rfb-option' of github.com:ThinLinc-Zeijlon/noVNC 2025-05-07 12:32:11 +02:00
Alexander Zeijlon
243d7fdd5f Disable setting showDotCursor in RFB constructor
This has been deprecated for around six years now. Let's remove the
deprecation warning and disable setting showDotCursor via the options
parameter.
2025-05-07 09:20:34 +02:00
Pierre Ossman
88749fc0f9 Document new JSON settings files
This mechanism was added in 438e5b3, but we forgot to document it.
2025-04-28 09:59:37 +02:00
Pierre Ossman
a22857c99c Document new behaviour of host/port/encrypt/path
This changed in 96c76f7, but we forgot to adjust the documentation for
the parameters.
2025-04-28 09:55:49 +02:00
Alexander Zeijlon
d7a37730e6 Merge branch 'master' of github.com:Leostruka/noVNC 2025-04-15 12:29:07 +02:00
leandro ostruka
6f9edb1d4a Fix typo in Portuguese translation for "Show extra keys" 2025-04-12 11:36:30 -03:00
Liao Peiyuan
980a49e5c5 Fix typo in error-handler.js 2025-04-10 12:29:19 -07:00
Pierre Ossman
8edb3d282e Close VideoFrame after H.264 detection
Otherwise browser will complain when it is garbage collected.
2025-04-08 15:42:20 +02:00
Pierre Ossman
154653523c Only include valid translations in .json files
Fuzzy translations might be incorrect, and obsolete translations aren't
used anywhere.
2025-03-25 09:02:39 +01:00
Samuel Mannehed
6010c9da04 Update comment reference missed in previous commit
Should have been part of f0a39cd357
2025-03-24 22:33:09 +01:00
Samuel Mannehed
f0a39cd357 Fix appearance of extra key buttons
Since the extra keys panel is quite narrow in width, a max-width style
resulted in the buttons almost disappearing. That rule was only intended
for elements inside the settings panel.

Broken by commit 14f9ea5880.

Another minor error that is also fixed by this commit is that the
clipboard textarea no longer incorrectly gets a left margin of 6px.

Fixes #1946.
2025-03-24 22:30:17 +01:00
23 changed files with 456 additions and 193 deletions

View File

@@ -6,7 +6,7 @@
* See README.md for usage and integration instructions.
*/
// Fallback for all uncought errors
// Fallback for all uncaught errors
function handleError(event, err) {
try {
const msg = document.getElementById('noVNC_fallback_errormsg');

View File

@@ -19,7 +19,7 @@
"Keyboard": "Teclado",
"Show keyboard": "Mostrar teclado",
"Extra keys": "Teclas adicionais",
"Show extra keys": "Mostar teclas adicionais",
"Show extra keys": "Mostrar teclas adicionais",
"Ctrl": "Ctrl",
"Toggle Ctrl": "Pressionar/soltar Ctrl",
"Alt": "Alt",

View File

@@ -475,15 +475,6 @@ html {
margin: 5px;
}
.noVNC_panel button,
.noVNC_panel select,
.noVNC_panel textarea,
.noVNC_panel input:not([type=checkbox]):not([type=radio]) {
margin-left: 6px;
/* Prevent inputs in panels from being too wide */
max-width: calc(100% - 6px - var(--input-xpadding) * 2);
}
.noVNC_panel .noVNC_heading {
background-color: var(--novnc-blue);
border-radius: 6px;
@@ -621,6 +612,15 @@ html {
list-style: none;
padding: 0px;
}
#noVNC_settings button,
#noVNC_settings select,
#noVNC_settings textarea,
#noVNC_settings input:not([type=checkbox]):not([type=radio]) {
margin-left: 6px;
/* Prevent inputs in settings from being too wide */
max-width: calc(100% - 6px - var(--input-xpadding) * 2);
}
#noVNC_setting_port {
width: 80px;
}

View File

@@ -20,7 +20,7 @@ import * as WebUtil from "./webutil.js";
const PAGE_TITLE = "noVNC";
const LINGUAS = ["cs", "de", "el", "es", "fr", "it", "ja", "ko", "nl", "pl", "pt_BR", "ru", "sv", "tr", "zh_CN", "zh_TW"];
const LINGUAS = ["cs", "de", "el", "es", "fr", "hr", "it", "ja", "ko", "nl", "pl", "pt_BR", "ru", "sv", "tr", "zh_CN", "zh_TW"];
const UI = {

View File

@@ -119,18 +119,33 @@ export default class JPEGDecoder {
let extra = 0;
if (type === 0xDA) {
// start of scan
extra += 2;
if (sock.rQwait("JPEG", length-2 + 2, 4)) {
return null;
}
let len = sock.rQlen();
let data = sock.rQpeekBytes(len, false);
while (true) {
if (sock.rQwait("JPEG", length-2+extra, 4)) {
let idx = data.indexOf(0xFF, length-2+extra);
if (idx === -1) {
sock.rQwait("JPEG", Infinity, 4);
return null;
}
let data = sock.rQpeekBytes(length-2+extra, false);
if (data.at(-2) === 0xFF && data.at(-1) !== 0x00 &&
!(data.at(-1) >= 0xD0 && data.at(-1) <= 0xD7)) {
extra -= 2;
break;
if (idx === len-1) {
sock.rQwait("JPEG", Infinity, 4);
return null;
}
extra++;
if (data.at(idx+1) === 0x00 ||
(data.at(idx+1) >= 0xD0 && data.at(idx+1) <= 0xD7)) {
extra = idx+2 - (length-2);
continue;
}
extra = idx - (length-2);
break;
}
}

View File

@@ -521,6 +521,9 @@ export default class Display {
return;
}
this.drawImage(a.img, a.x, a.y);
// This helps the browser free the memory right
// away, rather than ballooning
a.img.src = "";
} else {
a.img._noVNCDisplay = this;
a.img.addEventListener('load', this._resumeRenderQ);

View File

@@ -300,10 +300,6 @@ export default class RFB extends EventTargetMixin {
this._resizeSession = false;
this._showDotCursor = false;
if (options.showDotCursor !== undefined) {
Log.Warn("Specifying showDotCursor as a RFB constructor argument is deprecated");
this._showDotCursor = options.showDotCursor;
}
this._qualityLevel = 6;
this._compressionLevel = 2;

View File

@@ -117,7 +117,7 @@ async function _checkWebCodecsH264DecodeSupport() {
let error = null;
let decoder = new VideoDecoder({
output: (frame) => { gotframe = true; },
output: (frame) => { gotframe = true; frame.close(); },
error: (e) => { error = e; },
});
let chunk = new EncodedVideoChunk({

View File

@@ -124,6 +124,10 @@ export default class Websock {
return res >>> 0;
}
rQlen() {
return this._rQlen - this._rQi;
}
rQshiftStr(len) {
let str = "";
// Handle large arrays in steps to avoid long strings on the stack

10
docker-compose.yml Normal file
View File

@@ -0,0 +1,10 @@
version: '3.8'
services:
novnc:
image: nginx:alpine
ports:
- "4445:80"
volumes:
- ./noVNC:/usr/share/nginx/html:ro
restart: unless-stopped

View File

@@ -25,8 +25,27 @@ server and setting up a WebSocket proxy to the VNC server.
## Parameters
The noVNC application can be controlled by including certain settings in the
query string. Currently the following options are available:
The noVNC application can be controlled via a number of settings. All of
them are available in the UI for the user to change, but they can also
be set via other means:
* Via the URL, either as a query parameter:
```
https://www.example.com/vnc.html?reconnect=0&shared=1
```
or as a fragment:
```
https://www.example.com/vnc.html#reconnect=0&shared=1
```
The latter might be preferred as it is not sent to the server.
* Via the files `defaults.json` and `mandatory.json`
Currently, the following options are available:
* `autoconnect` - Automatically connect as soon as the page has finished
loading.
@@ -37,13 +56,18 @@ query string. Currently the following options are available:
* `reconnect_delay` - How long to wait in milliseconds before attempting to
reconnect.
* `host` - The WebSocket host to connect to.
* `host` - The WebSocket host to connect to. This setting is deprecated
in favor of specifying a URL in `path`.
* `port` - The WebSocket port to connect to.
* `port` - The WebSocket port to connect to. This setting is deprecated
in favor of specifying a URL in `path`.
* `encrypt` - If TLS should be used for the WebSocket connection.
* `encrypt` - If TLS should be used for the WebSocket connection. This
setting is deprecated in favor of specifying a URL in `path`.
* `path` - The WebSocket path to use.
* `path` - The WebSocket URL to use. It can be either an absolute URL,
or a URL relative vnc.html. If `host` is specified, then `path` will
be interpreted as the path component in the URL instead.
* `password` - The password sent to the server, if required.

View File

@@ -2,24 +2,20 @@
"name": "@novnc/novnc",
"version": "1.6.0",
"description": "An HTML5 VNC client",
"browser": "lib/rfb",
"directories": {
"lib": "lib",
"doc": "docs",
"test": "tests"
},
"type": "module",
"files": [
"lib",
"core",
"vendor",
"AUTHORS",
"VERSION",
"docs/API.md",
"docs/LIBRARY.md",
"docs/LICENSE*"
],
"exports": "./core/rfb.js",
"scripts": {
"lint": "eslint app core po/po2js po/xgettext-html tests utils",
"test": "karma start karma.conf.js",
"prepublish": "node ./utils/convert.js --clean"
"test": "karma start karma.conf.cjs"
},
"repository": {
"type": "git",

View File

@@ -2,7 +2,7 @@ all:
.PHONY: update-po update-js update-pot
.PHONY: FORCE
LINGUAS := cs de el es fr it ja ko nl pl pt_BR ru sv tr zh_CN zh_TW
LINGUAS := cs de el es fr hr it ja ko nl pl pt_BR ru sv tr zh_CN zh_TW
VERSION := $(shell grep '"version"' ../package.json | cut -d '"' -f 4)

338
po/hr.po Normal file
View File

@@ -0,0 +1,338 @@
# Croatian translations for noVNC package
# Hrvatski prijevod za noVNC paket
# Copyright (C) 2025 The noVNC authors
# This file is distributed under the same license as the noVNC package.
# Milo Ivir <mail@milotype.de>, 2025.
#
msgid ""
msgstr ""
"Project-Id-Version: noVNC 1.6.0\n"
"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
"POT-Creation-Date: 2025-02-14 10:14+0100\n"
"PO-Revision-Date: 2025-08-25 18:24+0200\n"
"Last-Translator: Milo Ivir <mail@mivirtype.de>\n"
"Language-Team: \n"
"Language: hr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 3.7\n"
#: ../app/ui.js:84
msgid ""
"Running without HTTPS is not recommended, crashes or other issues are likely."
msgstr ""
"Pokretanje bez HTTPS-a se ne preporučuje, vjerojatno će se dogoditi prekidi "
"rada ili drugi problemi."
#: ../app/ui.js:413
msgid "Connecting..."
msgstr "Povezivanje …"
#: ../app/ui.js:420
msgid "Disconnecting..."
msgstr "Odspajanje …"
#: ../app/ui.js:426
msgid "Reconnecting..."
msgstr "Ponovno povezivanje …"
#: ../app/ui.js:431
msgid "Internal error"
msgstr "Interna greška"
#: ../app/ui.js:1079
msgid "Failed to connect to server: "
msgstr "Povezivanje sa serverom nije uspjelo: "
#: ../app/ui.js:1145
msgid "Connected (encrypted) to "
msgstr "Povezano (šifrirano) na "
#: ../app/ui.js:1147
msgid "Connected (unencrypted) to "
msgstr "Povezano (nešifrirano) na "
#: ../app/ui.js:1170
msgid "Something went wrong, connection is closed"
msgstr "Nešto nije u redu, veza je zatvorena"
#: ../app/ui.js:1173
msgid "Failed to connect to server"
msgstr "Povezivanje sa serverom nije uspjelo"
#: ../app/ui.js:1185
msgid "Disconnected"
msgstr "Odspojeno"
#: ../app/ui.js:1200
msgid "New connection has been rejected with reason: "
msgstr "Nova veza je odbijena s razlogom: "
#: ../app/ui.js:1203
msgid "New connection has been rejected"
msgstr "Nova veza je odbijena"
#: ../app/ui.js:1269
msgid "Credentials are required"
msgstr "Podaci za prijavu su obavezni"
#: ../vnc.html:106
msgid "noVNC encountered an error:"
msgstr "noVNC je naišao na grešku:"
#: ../vnc.html:116
msgid "Hide/Show the control bar"
msgstr "Sakrij/Prikaži traku kontrola"
#: ../vnc.html:125
msgid "Drag"
msgstr "Povuci"
#: ../vnc.html:125
msgid "Move/Drag viewport"
msgstr "Pomakni/Povuci vidljivo područje"
#: ../vnc.html:131
msgid "Keyboard"
msgstr "Tipkovnica"
#: ../vnc.html:131
msgid "Show keyboard"
msgstr "Prikaži tipkovnicu"
#: ../vnc.html:136
msgid "Extra keys"
msgstr "Dodatne tipke"
#: ../vnc.html:136
msgid "Show extra keys"
msgstr "Prikaži dodatne tipke"
#: ../vnc.html:141
msgid "Ctrl"
msgstr "Ctrl"
#: ../vnc.html:141
msgid "Toggle Ctrl"
msgstr "Uključi/Isključi Ctrl"
#: ../vnc.html:144
msgid "Alt"
msgstr "Alt"
#: ../vnc.html:144
msgid "Toggle Alt"
msgstr "Uključi/Isključi Alt"
#: ../vnc.html:147
msgid "Toggle Windows"
msgstr "Uključi/Isključi Windows"
#: ../vnc.html:147
msgid "Windows"
msgstr "Windows"
#: ../vnc.html:150
msgid "Send Tab"
msgstr "Pošalji tabulator"
#: ../vnc.html:150
msgid "Tab"
msgstr "Tabulator"
#: ../vnc.html:153
msgid "Esc"
msgstr "Esc"
#: ../vnc.html:153
msgid "Send Escape"
msgstr "Pošalji Escape"
#: ../vnc.html:156
msgid "Ctrl+Alt+Del"
msgstr "Ctrl + Alt + Del"
#: ../vnc.html:156
msgid "Send Ctrl-Alt-Del"
msgstr "Pošalji Ctrl+Alt+Del"
#: ../vnc.html:163
msgid "Shutdown/Reboot"
msgstr "Isključi/Ponovo pokreni"
#: ../vnc.html:163
msgid "Shutdown/Reboot..."
msgstr "Isključi/Ponovo pokreni …"
#: ../vnc.html:169
msgid "Power"
msgstr "Napajanje"
#: ../vnc.html:171
msgid "Shutdown"
msgstr "Isključi"
#: ../vnc.html:172
msgid "Reboot"
msgstr "Ponovo pokreni"
#: ../vnc.html:173
msgid "Reset"
msgstr "Resetiraj"
#: ../vnc.html:178 ../vnc.html:184
msgid "Clipboard"
msgstr "Međuspremnik"
#: ../vnc.html:186
msgid "Edit clipboard content in the textarea below."
msgstr "Uredi sadržaj međuspremnika u donjem području teksta."
#: ../vnc.html:194
msgid "Full screen"
msgstr "Cjeloekranski prikaz"
#: ../vnc.html:199 ../vnc.html:205
msgid "Settings"
msgstr "Postavke"
#: ../vnc.html:211
msgid "Shared mode"
msgstr "Dijeljeni modus"
#: ../vnc.html:218
msgid "View only"
msgstr "Samo prikaz"
#: ../vnc.html:226
msgid "Clip to window"
msgstr "Isijeci na veličinu prozora"
#: ../vnc.html:231
msgid "Scaling mode:"
msgstr "Modus skaliranja:"
#: ../vnc.html:233
msgid "None"
msgstr "Bez"
#: ../vnc.html:234
msgid "Local scaling"
msgstr "Lokalno skaliranje"
#: ../vnc.html:235
msgid "Remote resizing"
msgstr "Daljinsko mijenjanje veličine"
#: ../vnc.html:240
msgid "Advanced"
msgstr "Napredno"
#: ../vnc.html:243
msgid "Quality:"
msgstr "Kvaliteta:"
#: ../vnc.html:247
msgid "Compression level:"
msgstr "Razina kompresije:"
#: ../vnc.html:252
msgid "Repeater ID:"
msgstr "ID repetitora:"
#: ../vnc.html:256
msgid "WebSocket"
msgstr "WebSocket"
#: ../vnc.html:261
msgid "Encrypt"
msgstr "Šifriraj"
#: ../vnc.html:266
msgid "Host:"
msgstr "Host:"
#: ../vnc.html:270
msgid "Port:"
msgstr "Priključak:"
#: ../vnc.html:274
msgid "Path:"
msgstr "Putanja:"
#: ../vnc.html:283
msgid "Automatic reconnect"
msgstr "Automatsko ponovno povezivanje"
#: ../vnc.html:288
msgid "Reconnect delay (ms):"
msgstr "Kašnjenje ponovnog povezivanja (ms):"
#: ../vnc.html:295
msgid "Show dot when no cursor"
msgstr "Prikaži točku kada nema pokazivača"
#: ../vnc.html:302
msgid "Logging:"
msgstr "Zapisivanje:"
#: ../vnc.html:311
msgid "Version:"
msgstr "Verzija:"
#: ../vnc.html:319
msgid "Disconnect"
msgstr "Odspoji"
#: ../vnc.html:342
msgid "Connect"
msgstr "Poveži"
#: ../vnc.html:351
msgid "Server identity"
msgstr "Identitet servera"
#: ../vnc.html:354
msgid "The server has provided the following identifying information:"
msgstr "Server je pružio sljedeće identifikacijske podatke:"
#: ../vnc.html:357
msgid "Fingerprint:"
msgstr "Otisak:"
#: ../vnc.html:361
msgid ""
"Please verify that the information is correct and press \"Approve\". "
"Otherwise press \"Reject\"."
msgstr ""
"Provjeri jesu li podaci točni i pritisni „Odobri“. U suprotnom pritisni "
"„Odbaci“."
#: ../vnc.html:366
msgid "Approve"
msgstr "Odobri"
#: ../vnc.html:367
msgid "Reject"
msgstr "Odbij"
#: ../vnc.html:375
msgid "Credentials"
msgstr "Podaci za prijavu"
#: ../vnc.html:379
msgid "Username:"
msgstr "Korisničko ime:"
#: ../vnc.html:383
msgid "Password:"
msgstr "Lozinka:"
#: ../vnc.html:387
msgid "Send credentials"
msgstr "Pošalji podatke za prijavu"
#: ../vnc.html:396
msgid "Cancel"
msgstr "Odustani"

View File

@@ -17,9 +17,9 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
const { program } = require('commander');
const fs = require('fs');
const pofile = require("pofile");
import { program } from 'commander';
import fs from 'fs';
import pofile from "pofile";
program
.argument('<input>')
@@ -32,6 +32,8 @@ let po = pofile.parse(data);
const bodyPart = po.items
.filter(item => item.msgid !== "")
.filter(item => item.msgstr[0] !== "")
.filter(item => !item.flags.fuzzy)
.filter(item => !item.obsolete)
.map(item => " " + JSON.stringify(item.msgid) + ": " + JSON.stringify(item.msgstr[0]))
.join(",\n");

View File

@@ -100,7 +100,7 @@ msgstr "Teclas adicionais"
#: ../vnc.html:89
msgid "Show extra keys"
msgstr "Mostar teclas adicionais"
msgstr "Mostrar teclas adicionais"
#: ../vnc.html:94
msgid "Ctrl"

View File

@@ -5,9 +5,9 @@
* Licensed under MPL 2.0 (see LICENSE.txt)
*/
const { program } = require('commander');
const jsdom = require("jsdom");
const fs = require("fs");
import { program } from 'commander';
import jsdom from 'jsdom';
import fs from 'fs';
program
.argument('<INPUT...>')
@@ -106,7 +106,7 @@ let output = "";
for (let str in strings) {
output += "#:";
for (location in strings[str]) {
for (let location in strings[str]) {
output += " " + location;
}
output += "\n";

View File

@@ -1,4 +1,4 @@
import * as chai from '../node_modules/chai/chai.js';
import * as chai from '../node_modules/chai/index.js';
import sinon from '../node_modules/sinon/pkg/sinon-esm.js';
import sinonChai from '../node_modules/sinon-chai/lib/sinon-chai.js';

View File

@@ -384,10 +384,11 @@ describe('Display/Canvas helper', function () {
});
it('should draw an image from an image object on type "img" (if complete)', function () {
const img = { complete: true };
display.drawImage = sinon.spy();
display._renderQPush({ type: 'img', x: 3, y: 4, img: { complete: true } });
display._renderQPush({ type: 'img', x: 3, y: 4, img: img });
expect(display.drawImage).to.have.been.calledOnce;
expect(display.drawImage).to.have.been.calledWith({ complete: true }, 3, 4);
expect(display.drawImage).to.have.been.calledWith(img, 3, 4);
});
});
});

View File

@@ -47,6 +47,20 @@ describe('Websock', function () {
});
});
describe('rQlen())', function () {
it('should return the number of buffered bytes in the receive queue', function () {
websock._receiveData(new Uint8Array([0xab, 0xcd, 0x12, 0x34,
0x88, 0xee, 0x11, 0x33]));
expect(sock.rQlen()).to.equal(8);
sock.rQshift8();
expect(sock.rQlen()).to.equal(7);
sock.rQshift16();
expect(sock.rQlen()).to.equal(5);
sock.rQshift32();
expect(sock.rQlen()).to.equal(1);
});
});
describe('rQshiftStr', function () {
it('should shift the given number of bytes off of the receive queue and return a string', function () {
websock._receiveData(new Uint8Array([0xab, 0xcd, 0x12, 0x34,

View File

@@ -1,140 +0,0 @@
#!/usr/bin/env node
const path = require('path');
const { program } = require('commander');
const fs = require('fs');
const fse = require('fs-extra');
const babel = require('@babel/core');
program
.option('-m, --with-source-maps [type]', 'output source maps when not generating a bundled app (type may be empty for external source maps, inline for inline source maps, or both) ')
.option('--clean', 'clear the lib folder before building')
.parse(process.argv);
// the various important paths
const paths = {
main: path.resolve(__dirname, '..'),
core: path.resolve(__dirname, '..', 'core'),
vendor: path.resolve(__dirname, '..', 'vendor'),
libDirBase: path.resolve(__dirname, '..', 'lib'),
};
// util.promisify requires Node.js 8.x, so we have our own
function promisify(original) {
return function promiseWrap() {
const args = Array.prototype.slice.call(arguments);
return new Promise((resolve, reject) => {
original.apply(this, args.concat((err, value) => {
if (err) return reject(err);
resolve(value);
}));
});
};
}
const writeFile = promisify(fs.writeFile);
const readdir = promisify(fs.readdir);
const lstat = promisify(fs.lstat);
const ensureDir = promisify(fse.ensureDir);
const babelTransformFile = promisify(babel.transformFile);
// walkDir *recursively* walks directories trees,
// calling the callback for all normal files found.
function walkDir(basePath, cb, filter) {
return readdir(basePath)
.then((files) => {
const paths = files.map(filename => path.join(basePath, filename));
return Promise.all(paths.map(filepath => lstat(filepath)
.then((stats) => {
if (filter !== undefined && !filter(filepath, stats)) return;
if (stats.isSymbolicLink()) return;
if (stats.isFile()) return cb(filepath);
if (stats.isDirectory()) return walkDir(filepath, cb, filter);
})));
});
}
function makeLibFiles(sourceMaps) {
// NB: we need to make a copy of babelOpts, since babel sets some defaults on it
const babelOpts = () => ({
plugins: [],
presets: [
[ '@babel/preset-env',
{ modules: 'commonjs' } ]
],
ast: false,
sourceMaps: sourceMaps,
});
fse.ensureDirSync(paths.libDirBase);
const outFiles = [];
const handleDir = (vendorRewrite, inPathBase, filename) => Promise.resolve()
.then(() => {
const outPath = path.join(paths.libDirBase, path.relative(inPathBase, filename));
if (path.extname(filename) !== '.js') {
return; // skip non-javascript files
}
return Promise.resolve()
.then(() => ensureDir(path.dirname(outPath)))
.then(() => {
const opts = babelOpts();
// Adjust for the fact that we move the core files relative
// to the vendor directory
if (vendorRewrite) {
opts.plugins.push(["import-redirect",
{"root": paths.libDirBase,
"redirect": { "vendor/(.+)": "./vendor/$1"}}]);
}
return babelTransformFile(filename, opts)
.then((res) => {
console.log(`Writing ${outPath}`);
const {map} = res;
let {code} = res;
if (sourceMaps === true) {
// append URL for external source map
code += `\n//# sourceMappingURL=${path.basename(outPath)}.map\n`;
}
outFiles.push(`${outPath}`);
return writeFile(outPath, code)
.then(() => {
if (sourceMaps === true || sourceMaps === 'both') {
console.log(` and ${outPath}.map`);
outFiles.push(`${outPath}.map`);
return writeFile(`${outPath}.map`, JSON.stringify(map));
}
});
});
});
});
Promise.resolve()
.then(() => {
const handler = handleDir.bind(null, false, paths.main);
return walkDir(paths.vendor, handler);
})
.then(() => {
const handler = handleDir.bind(null, true, paths.core);
return walkDir(paths.core, handler);
})
.catch((err) => {
console.error(`Failure converting modules: ${err}`);
process.exit(1);
});
}
let options = program.opts();
if (options.clean) {
console.log(`Removing ${paths.libDirBase}`);
fse.removeSync(paths.libDirBase);
}
makeLibFiles(options.withSourceMaps);

View File

@@ -7,7 +7,7 @@
"use strict";
const fs = require('fs');
import fs from 'fs';
let showHelp = process.argv.length === 2;
let filename;