From 416f510840527d58e24f781c95f28d9a23d4d259 Mon Sep 17 00:00:00 2001 From: Etienne Rossignon Date: Tue, 1 Jun 2021 08:27:21 +0200 Subject: [PATCH] fix issue #1192 & only compress VFS if --compress is set --- lib/producer.ts | 72 ++++++++++++++++++++------ prelude/bootstrap.js | 16 ++++-- test/test-1191/.gitignore | 3 ++ test/test-1191/main.js | 73 ++++++++++++++++++++------- test/test-1192/.gitignore | 1 + test/test-1192/main.js | 65 ++++++++++++++++++++++++ test/test-1192/package.json | 24 +++++++++ test/test-1192/public/views/index.pug | 8 +++ test/test-1192/src/index.js | 47 +++++++++++++++++ 9 files changed, 270 insertions(+), 39 deletions(-) create mode 100644 test/test-1191/.gitignore create mode 100644 test/test-1192/.gitignore create mode 100644 test/test-1192/main.js create mode 100644 test/test-1192/package.json create mode 100644 test/test-1192/public/views/index.pug create mode 100644 test/test-1192/src/index.js diff --git a/lib/producer.ts b/lib/producer.ts index df83af01d..c75e9098e 100644 --- a/lib/producer.ts +++ b/lib/producer.ts @@ -227,8 +227,7 @@ function nativePrebuildInstall(target: Target, nodeFile: string) { } execSync( - `${prebuild} -t ${nodeVersion} --platform ${platform[target.platform] - } --arch ${target.arch}`, + `${prebuild} -t ${nodeVersion} --platform ${platform[target.platform]} --arch ${target.arch}`, { cwd: dir } ); fs.copyFileSync(nodeFile, nativeFile); @@ -246,31 +245,67 @@ interface ProducerOptions { doCompress: CompressType; } +/** + * instead of creating a vfs dicionnary with actual path as key + * we use a compression mechanism that can reduce significantly + * the memory footprint of the vfs in the code. + * + * without vfs compression: + * + * vfs = { + * "/folder1/folder2/file1.js": {}; + * "/folder1/folder2/folder3/file2.js": {}; + * "/folder1/folder2/folder3/file3.js": {}; + * } + * + * with compression : + * + * fileDictionary = { + * "folder1": "1", + * "folder2": "2", + * "file1": "3", + * "folder3": "4", + * "file2": "5", + * "file3": "6", + * } + * vfs = { + * "/1/2/3": {}; + * "/1/2/4/5": {}; + * "/1/2/4/6": {}; + * } + * + * note: the key is computed in base36 for further compression. + */ const fileDictionary: { [key: string]: string } = {}; let counter = 0; -function replace(k: string) { - let existingKey = fileDictionary[k]; +function getOrCreateHash(fileOrFolderName: string) { + let existingKey = fileDictionary[fileOrFolderName]; if (!existingKey) { const newkey = counter; counter += 1; existingKey = newkey.toString(36); - fileDictionary[k] = existingKey; + fileDictionary[fileOrFolderName] = existingKey; } return existingKey; } -const separator = '$'; - -function makeKey(filename: string, slash: string): string { - const a = filename.split(slash).map(replace).join(separator); - return a; +const separator = '/'; + +function makeKey( + doCompression: CompressType, + fullpath: string, + slash: string +): string { + if (doCompression === CompressType.None) return fullpath; + return fullpath.split(slash).map(getOrCreateHash).join(separator); } + export default function producer({ backpack, bakes, slash, target, symLinks, - doCompress + doCompress, }: ProducerOptions) { return new Promise((resolve, reject) => { if (!Buffer.alloc) { @@ -289,7 +324,7 @@ export default function producer({ for (const stripe of stripes) { let { snap } = stripe; snap = snapshotify(snap, slash); - const vfsKey = makeKey(snap, slash); + const vfsKey = makeKey(doCompress, snap, slash); if (!vfs[vfsKey]) vfs[vfsKey] = {}; } @@ -298,8 +333,8 @@ export default function producer({ for (const [key, value] of Object.entries(symLinks)) { const k = snapshotify(key, slash); const v = snapshotify(value, slash); - const vfsKey = makeKey(k, slash); - snapshotSymLinks[vfsKey] = makeKey(v, slash); + const vfsKey = makeKey(doCompress, k, slash); + snapshotSymLinks[vfsKey] = makeKey(doCompress, v, slash); } let meter: streamMeter.StreamMeter; @@ -350,7 +385,7 @@ export default function producer({ const { store } = prevStripe; let { snap } = prevStripe; snap = snapshotify(snap, slash); - const vfsKey = makeKey(snap, slash); + const vfsKey = makeKey(doCompress, snap, slash); vfs[vfsKey][store] = [track, meter.bytes]; track += meter.bytes; } @@ -377,7 +412,12 @@ export default function producer({ return cb(null, intoStream(Buffer.alloc(0))); } - cb(null, pipeMayCompressToNewMeter(intoStream(buffer || Buffer.from('')))); + cb( + null, + pipeMayCompressToNewMeter( + intoStream(buffer || Buffer.from('')) + ) + ); } ); } diff --git a/prelude/bootstrap.js b/prelude/bootstrap.js index 37bdb3826..67a3b427d 100644 --- a/prelude/bootstrap.js +++ b/prelude/bootstrap.js @@ -137,7 +137,7 @@ function readdirMountpoints(filename) { return path.dirname(interior) === filename; }) .map(({ interior, exterior }) => { - if (isRegExp(interior)) return path.replace(interior, exterior); + if (isRegExp(interior)) return filename.replace(interior, exterior); return path.basename(interior); }); } @@ -190,8 +190,8 @@ console.log(translateNth(["", "rw"], 0, "d:\\snapshot\\countly\\plugins-ext\\")) console.log(translateNth(["", "a+"], 0, "d:\\snapshot\\countly\\plugins-ext\\1234")); */ const dictRev = {}; -const separator = '$'; -let maxKey = Object.keys(DICT).reduce((p, c) => { +const separator = '/'; +let maxKey = Object.values(DICT).reduce((p, c) => { const cc = parseInt(c, 36); return cc > p ? cc : p; }, 0); @@ -208,6 +208,9 @@ function replace(k) { } function makeKey(filename, slash) { + if (!DOCOMPRESS) { + return filename; + } const a = filename.split(slash).map(replace).join(separator); return a || filename; } @@ -216,8 +219,11 @@ Object.entries(DICT).forEach(([k, v]) => { }); function toOriginal(fShort) { + if (!DOCOMPRESS) { + return fShort; + } return fShort - .split('$') + .split(separator) .map((x) => dictRev[x]) .join(sep); } @@ -483,7 +489,7 @@ function payloadFileSync(pointer) { (() => { process.pkg.path = {}; - process.pkg.path.resolve = () => { + process.pkg.path.resolve = function resolve() { const args = cloneArgs(arguments); args.unshift(path.dirname(ENTRYPOINT)); return path.resolve.apply(path, args); // eslint-disable-line prefer-spread diff --git a/test/test-1191/.gitignore b/test/test-1191/.gitignore new file mode 100644 index 000000000..aa3001828 --- /dev/null +++ b/test/test-1191/.gitignore @@ -0,0 +1,3 @@ +output* +test.sqlite +package-lock* diff --git a/test/test-1191/main.js b/test/test-1191/main.js index d98fdec9c..0e140ad1a 100644 --- a/test/test-1191/main.js +++ b/test/test-1191/main.js @@ -12,7 +12,8 @@ assert(__dirname === process.cwd()); /* eslint-disable no-unused-vars */ const target = process.argv[2] || 'host'; const ext = process.platform === 'win32' ? '.exe' : ''; -const output = './output' + ext; +const output1 = './output1' + ext; +const output2 = './output2' + ext; const input = './index.js'; // remove any possible left-over @@ -25,36 +26,72 @@ console.log('node version = ', version); const npmlog = utils.exec.sync('npm install'); console.log('npm log :', npmlog); -utils.pkg.sync(['--target', target, '--output', output, input], { - expect: 0, -}); - // ----------------------------------------------------------------------- // Execute programm outside pjg const logRef = utils.spawn.sync('node', [path.join(__dirname, input)], { cwd: __dirname, expect: 0, }); + if (logRef.replace(/\r|\n/g, '') !== '42') { console.log(`expecting 42 but got ${logRef}`); process.exit(1); } -const log = utils.spawn.sync(path.join(__dirname, output), [], { - cwd: __dirname, - expect: 1, - stdio: ['inherit', 'pipe', 'pipe'], -}); -if ( - !log.stderr.match( - /was not included into executable at compilation stage. Please recompile adding it as asset or script/ - ) -) { - console.log('expecting warning message'); - console.log('but got =', log.stderr); +function doTestWithCompression() { + console.log('doTestWithCompression'); + utils.pkg.sync( + ['--compress', 'Brotli', '--target', target, '--output', output1, input], + { + expect: 0, + } + ); + const log = utils.spawn.sync(path.join(__dirname, output1), [], { + cwd: __dirname, + expect: 0, + stdio: ['inherit', 'pipe', 'pipe'], + }); + return log; +} +function doTestNoCompression() { + console.log('doTestNoCompression'); + utils.pkg.sync(['--target', target, '--output', output2, input], { + expect: 0, + }); + const log = utils.spawn.sync(path.join(__dirname, output2), [], { + cwd: __dirname, + expect: 0, + stdio: ['inherit', 'pipe', 'pipe'], + }); + return log; +} +const logNoCompression = doTestNoCompression(); +if (logNoCompression.stderr !== '') { + console.log('NO COMPRESSION: expecting no error'); + console.log('but got =', logNoCompression.stderr); + process.exit(1); +} + +const logWithCompression = doTestWithCompression(); +if (logWithCompression.stderr !== '') { + console.log('NO COMPRESSION: expecting no error'); + console.log('but got =', logWithCompression.stderr); process.exit(1); } -utils.vacuum.sync(output); +// if ( +// !log.stderr.match( +// /was not included into executable at compilation stage. Please recompile adding it as asset or script/ +// ) +// ) { +// console.log('expecting warning message'); +// console.log('but got =', log.stderr); +// process.exit(1); +// } + +// now with compress + +utils.vacuum.sync(output1); +utils.vacuum.sync(output2); utils.vacuum.sync('node_modules'); utils.vacuum.sync('package-lock.json'); utils.vacuum.sync('test.sqlite'); diff --git a/test/test-1192/.gitignore b/test/test-1192/.gitignore new file mode 100644 index 000000000..849ddff3b --- /dev/null +++ b/test/test-1192/.gitignore @@ -0,0 +1 @@ +dist/ diff --git a/test/test-1192/main.js b/test/test-1192/main.js new file mode 100644 index 000000000..2c1756dbe --- /dev/null +++ b/test/test-1192/main.js @@ -0,0 +1,65 @@ +#!/usr/bin/env node + +'use strict'; + +const path = require('path'); +const assert = require('assert'); +const utils = require('../utils.js'); + +assert(!module.parent); +assert(__dirname === process.cwd()); + +/* eslint-disable no-unused-vars */ +const target = process.argv[2] || 'host'; +const ext = process.platform === 'win32' ? '.exe' : ''; +const cmd = process.platform === 'win32' ? '.cmd' : ''; +const output = './output' + ext; +const input = './package.json'; + +// remove any possible left-over +utils.vacuum.sync('./node_modules'); + +const version = utils.exec.sync('node --version'); +console.log('node version = ', version); + +// launch `npm install` +const npmlog = utils.exec.sync('npm' + cmd + ' install'); +// console.log('npm log :', npmlog); + +utils.pkg.sync(['--target', target, '--output', output, input], { + expect: 0, +}); + +// ----------------------------------------------------------------------- +// Execute programm outside pjg +const logRef = utils.spawn.sync( + 'node', + [path.join(__dirname, 'src/index.js')], + { + cwd: __dirname, + expect: 0, + stdio: ['inherit', 'pipe', 'pipe'], + } +); + +const log = utils.spawn.sync(path.join(__dirname, output), [], { + cwd: __dirname, + expect: 0, + stdio: ['inherit', 'pipe', 'pipe'], +}); + +if (logRef.stdout !== log.stdout) { + console.log('expecting', logRef.stdout); + console.log('but got =', log.stdout); + process.exit(1); +} +if (logRef.stderr !== log.stderr) { + console.log('expecting', logRef.stderr); + console.log('but got =', log.stderr); + process.exit(1); +} + +utils.vacuum.sync(output); +utils.vacuum.sync('node_modules'); +utils.vacuum.sync('package-lock.json'); +console.log('Done'); diff --git a/test/test-1192/package.json b/test/test-1192/package.json new file mode 100644 index 000000000..6f8c74a8f --- /dev/null +++ b/test/test-1192/package.json @@ -0,0 +1,24 @@ +{ + "name": "pkg-express", + "version": "1.0.0", + "description": "Illustrates issue between pkg 5.2.X and express (works with with pkg pre 5.2.X).", + "repository": "", + "main": "src/index.js", + "bin": "src/index.js", + "license": "MIT", + "scripts": { + "postinstall": "npm run make", + "start": "node .", + "make": "pkg . -o ./dist/pkg-express.exe" + }, + "dependencies": { + "express": "^4.17.1", + "pug": "^3.0.2" + }, + "devDependencies": { + "pkg": "5.2.1" + }, + "pkg": { + "assets": "./public/views/*.pug" + } +} diff --git a/test/test-1192/public/views/index.pug b/test/test-1192/public/views/index.pug new file mode 100644 index 000000000..843b9e845 --- /dev/null +++ b/test/test-1192/public/views/index.pug @@ -0,0 +1,8 @@ + +html + head + title Hello World! + body + p Hello World! + + \ No newline at end of file diff --git a/test/test-1192/src/index.js b/test/test-1192/src/index.js new file mode 100644 index 000000000..e6d0775b0 --- /dev/null +++ b/test/test-1192/src/index.js @@ -0,0 +1,47 @@ +'use strict'; + +const http = require('http'); +const path = require('path'); +const express = require('express'); + +// Express app +const app = express(); +// Express views +app.set('views', path.join(__dirname, '../public/views')); +// Use pug as Express view engine +app.set('view engine', 'pug'); +// Match all routes +app.use('*', (_req, res) => { + res.render('index.pug'); +}); + +// Start HTTP server +const listener = http.createServer(app).listen(8080, () => { + console.info('Server started, listening on %d', listener.address().port); +}); + +// ------------------ now query he server +(async () => { + const options = { + hostname: '127.0.0.1', + path: '/', + method: 'GET', + port: 8080, + }; + + const req = http.request(options, (res) => { + console.log(`statusCode: ${res.statusCode}`); + res.on('data', (d) => { + process.stdout.write(d); + }); + }); + + req.on('error', (error) => { + console.error(error); + process.exit(1); + }); + req.on('close', () => { + process.exit(0); + }); + req.end(); +})();