diff --git a/README.md b/README.md index 5f8e7cbbb..fa20928e3 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,12 @@ In many cases, Pino is over 5x faster than alternatives. See the [Benchmarks](docs/benchmarks.md) document for comparisons. +### Bundling support + +Pino supports to being bundled using tools like webpack or esbuild. + +See [Bundling](docs/bundling.md) document for more informations. + ## The Team diff --git a/docs/bundling.md b/docs/bundling.md new file mode 100644 index 000000000..5d81a8b52 --- /dev/null +++ b/docs/bundling.md @@ -0,0 +1,30 @@ +# Bundling + +Due to its internal architecture based on Worker Threads, it is not possible to bundle Pino *without* generating additional files. + +In particular, a bundler must ensure that the following files are also bundle separately: + +* `lib/worker.js` from the `thread-stream` dependency +* `file.js` +* `lib/worker.js` +* `lib/worker-pipeline.js` +* Any transport used by the user (like `pino-pretty`) + +Once the files above have been generated, the bundler must also add information about the files above by injecting a code which sets `__bundlerPathsOverrides` in the `globalThis` object. + +The variable is a object whose keys are identifier for the files and the the values are the paths of files relative to the currently bundle files. + +Example: + +```javascript +// Inject this using your bundle plugin +globalThis.__bundlerPathsOverrides = { + 'thread-stream-worker': pinoWebpackAbsolutePath('./thread-stream-worker.js') + 'pino/file': pinoWebpackAbsolutePath('./pino-file.js'), + 'pino-worker': pinoWebpackAbsolutePath('./pino-worker.js'), + 'pino-pipeline-worker': pinoWebpackAbsolutePath('./pino-pipeline-worker.js'), + 'pino-pretty': pinoWebpackAbsolutePath('./pino-pretty.js'), +}; +``` + +Note that `pino/file`, `pino-worker`, `pino-pipeline-worker` and `thread-stream-worker` are required identifiers. Other identifiers are possible based on the user configuration. \ No newline at end of file diff --git a/lib/transport.js b/lib/transport.js index c2fca43ed..e998d21f4 100644 --- a/lib/transport.js +++ b/lib/transport.js @@ -81,6 +81,8 @@ function transport (fullOptions) { const { pipeline, targets, options = {}, worker = {}, caller = getCaller() } = fullOptions // This function call MUST stay in the top-level function of this module const callerRequire = createRequire(caller) + // This will be eventually modified by bundlers + const bundlerOverrides = '__bundlerPathsOverrides' in globalThis ? globalThis.__bundlerPathsOverrides : {} let target = fullOptions.target @@ -89,7 +91,7 @@ function transport (fullOptions) { } if (targets) { - target = join(__dirname, 'worker.js') + target = bundlerOverrides['pino-worker'] || join(__dirname, 'worker.js') options.targets = targets.map((dest) => { return { ...dest, @@ -97,7 +99,7 @@ function transport (fullOptions) { } }) } else if (fullOptions.pipeline) { - target = join(__dirname, 'worker-pipeline.js') + target = bundlerOverrides['pino-pipeline-worker'] || join(__dirname, 'worker-pipeline.js') options.targets = pipeline.map((dest) => { return { ...dest, @@ -109,6 +111,8 @@ function transport (fullOptions) { return buildStream(fixTarget(target), options, worker) function fixTarget (origin) { + origin = bundlerOverrides[origin] || origin + if (isAbsolute(origin) || origin.indexOf('file://') === 0) { return origin } diff --git a/lib/worker-pipeline.js b/lib/worker-pipeline.js index 7db451d95..32e7c0376 100644 --- a/lib/worker-pipeline.js +++ b/lib/worker-pipeline.js @@ -1,6 +1,7 @@ 'use strict' const EE = require('events') +const { realImport, realRequire } = require('real-require') const { pipeline, PassThrough } = require('stream') // This file is not checked by the code coverage tool, @@ -13,11 +14,11 @@ module.exports = async function ({ targets }) { let fn try { const toLoad = 'file://' + t.target - fn = (await import(toLoad)).default + fn = (await realImport(toLoad)).default } catch (error) { // See this PR for details: https://github.com/pinojs/thread-stream/pull/34 if ((error.code === 'ENOTDIR' || error.code === 'ERR_MODULE_NOT_FOUND')) { - fn = require(t.target) + fn = realRequire(t.target) } else { throw error } diff --git a/lib/worker.js b/lib/worker.js index 184ade043..9bfabcf5c 100644 --- a/lib/worker.js +++ b/lib/worker.js @@ -2,6 +2,7 @@ const pino = require('../pino.js') const build = require('pino-abstract-transport') +const { realImport, realRequire } = require('real-require') // This file is not checked by the code coverage tool, // as it is not reliable. @@ -13,11 +14,11 @@ module.exports = async function ({ targets }) { let fn try { const toLoad = 'file://' + t.target - fn = (await import(toLoad)).default + fn = (await realImport(toLoad)).default } catch (error) { // See this PR for details: https://github.com/pinojs/thread-stream/pull/34 if ((error.code === 'ENOTDIR' || error.code === 'ERR_MODULE_NOT_FOUND')) { - fn = require(t.target) + fn = realRequire(t.target) } else { throw error } diff --git a/package.json b/package.json index 42170f54a..46fcbb4ba 100644 --- a/package.json +++ b/package.json @@ -109,6 +109,7 @@ "pino-abstract-transport": "v0.4.0", "pino-std-serializers": "^4.0.0", "quick-format-unescaped": "^4.0.3", + "real-require": "^0.1.0", "safe-stable-stringify": "^2.1.0", "sonic-boom": "^2.2.1", "thread-stream": "^0.11.1" diff --git a/test/transport/bundlers-support.test.js b/test/transport/bundlers-support.test.js new file mode 100644 index 000000000..0e856eae7 --- /dev/null +++ b/test/transport/bundlers-support.test.js @@ -0,0 +1,106 @@ +'use strict' + +const os = require('os') +const { join } = require('path') +const { readFile } = require('fs').promises +const { watchFileCreated } = require('../helper') +const { test } = require('tap') +const pino = require('../../pino') + +const { pid } = process +const hostname = os.hostname() + +test('pino.transport with destination overriden by bundler', async ({ same, teardown }) => { + globalThis.__bundlerPathsOverrides = { + foobar: join(__dirname, '..', 'fixtures', 'to-file-transport.js') + } + + const destination = join( + os.tmpdir(), + '_' + Math.random().toString(36).substr(2, 9) + ) + const transport = pino.transport({ + target: 'foobar', + options: { destination } + }) + teardown(transport.end.bind(transport)) + const instance = pino(transport) + instance.info('hello') + await watchFileCreated(destination) + const result = JSON.parse(await readFile(destination)) + delete result.time + same(result, { + pid, + hostname, + level: 30, + msg: 'hello' + }) + + globalThis.__bundlerPathsOverrides = undefined +}) + +test('pino.transport with worker destination overriden by bundler', async ({ same, teardown }) => { + globalThis.__bundlerPathsOverrides = { + 'pino-worker': join(__dirname, '..', '..', 'lib/worker.js') + } + + const destination = join( + os.tmpdir(), + '_' + Math.random().toString(36).substr(2, 9) + ) + const transport = pino.transport({ + targets: [ + { + target: join(__dirname, '..', 'fixtures', 'to-file-transport.js'), + options: { destination } + } + ] + }) + teardown(transport.end.bind(transport)) + const instance = pino(transport) + instance.info('hello') + await watchFileCreated(destination) + const result = JSON.parse(await readFile(destination)) + delete result.time + same(result, { + pid, + hostname, + level: 30, + msg: 'hello' + }) + + globalThis.__bundlerPathsOverrides = undefined +}) + +test('pino.transport with worker-pipeline destination overriden by bundler', async ({ same, teardown }) => { + globalThis.__bundlerPathsOverrides = { + 'pino-pipeline-worker': join(__dirname, '..', '..', 'lib/worker-pipeline.js') + } + + const destination = join( + os.tmpdir(), + '_' + Math.random().toString(36).substr(2, 9) + ) + const transport = pino.transport({ + pipeline: [ + { + target: join(__dirname, '..', 'fixtures', 'to-file-transport.js'), + options: { destination } + } + ] + }) + teardown(transport.end.bind(transport)) + const instance = pino(transport) + instance.info('hello') + await watchFileCreated(destination) + const result = JSON.parse(await readFile(destination)) + delete result.time + same(result, { + pid, + hostname, + level: 30, + msg: 'hello' + }) + + globalThis.__bundlerPathsOverrides = undefined +})