Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Support bundlers. #1209

Merged
merged 3 commits into from Nov 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Expand Up @@ -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.
ShogunPanda marked this conversation as resolved.
Show resolved Hide resolved

<a name="team"></a>
## The Team

Expand Down
30 changes: 30 additions & 0 deletions 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.
8 changes: 6 additions & 2 deletions lib/transport.js
Expand Up @@ -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

Expand All @@ -89,15 +91,15 @@ 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,
target: fixTarget(dest.target)
}
})
} 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,
Expand All @@ -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
}
Expand Down
5 changes: 3 additions & 2 deletions 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,
Expand All @@ -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
}
Expand Down
5 changes: 3 additions & 2 deletions lib/worker.js
Expand Up @@ -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.
Expand All @@ -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
}
Expand Down
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -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"
Expand Down
106 changes: 106 additions & 0 deletions 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
})