Repository: pinojs/pino (js)

Issue:
Pino fails to load on node 14.15.1 and TS 4.1.2
Here's the following stack trace:

```
error Uncaught Exception received! TypeError: Cannot read property 'level' of undefined
    at genLsCache (/Users/yagiz/Projects/PROJECT/node_modules/pino/lib/levels.js:55:45)
    at Object.pino [as default] (/Users/yagiz/Projects/PROJECT/node_modules/pino/pino.js:175:3)
    at Object.<anonymous> (/Users/yagiz/Projects/PROJECT/src/library/logger.ts:28:20)
    at Module._compile (internal/modules/cjs/loader.js:1063:30)
    at Module.m._compile (/Users/yagiz/Projects/PROJECT/node_modules/ts-node/src/index.ts:1056:23)
    at Module._extensions..js (internal/modules/cjs/loader.js:1092:10)
    at Object.require.extensions.<computed> [as .ts] (/Users/yagiz/Projects/PROJECT/node_modules/ts-node/src/index.ts:1059:12)
    at Module.load (internal/modules/cjs/loader.js:928:32)
    at Function.Module._load (internal/modules/cjs/loader.js:769:14)
    at Module.require (internal/modules/cjs/loader.js:952:19)
    at require (internal/modules/cjs/helpers.js:88:18)
    at Object.<anonymous> (/Users/yagiz/Projects/PROJECT/src/consumer.ts:7:1)
    at Module._compile (internal/modules/cjs/loader.js:1063:30)
    at Module.m._compile (/Users/yagiz/Projects/PROJECT/node_modules/ts-node/src/index.ts:1056:23)
    at Module._extensions..js (internal/modules/cjs/loader.js:1092:10)
    at Object.require.extensions.<computed> [as .ts] (/Users/yagiz/Projects/PROJECT/node_modules/ts-node/src/index.ts:1059:12) +0ms
```

My tsconfig is:
```json
{
  "compilerOptions": {
    "target": "es2020",
    "module": "commonjs",
    "lib": ["ES2015", "ES6"],
    "rootDir": "./src",
    "outDir": "./build",
    "esModuleInterop": true,
    "strict": true,
    "sourceMap": true,
    "moduleResolution": "node"
  },
  "exclude": [
    "node_modules"
  ]
}
```

my logger file:
```typescript
import Pino from 'pino'
export default Pino()
```

Here's my dependencies for you to reproduce:

```json
  "dependencies": {
    "pino": "^6.7.0"
  },
  "devDependencies": {
    "@types/amqplib": "^0.5.16",
    "@types/couchbase": "^2.4.2",
    "@types/node": "^14.14.12",
    "@types/pino": "^6.3.4",
    "@typescript-eslint/eslint-plugin": "^4.9.1",
    "@typescript-eslint/parser": "^4.9.1",
    "dotenv": "^8.2.0",
    "eslint": "^7.15.0",
    "eslint-config-google": "^0.14.0",
    "nodemon": "^2.0.6",
    "pino-pretty": "^4.3.0",
    "ts-node": "^9.1.1",
    "typescript": "^4.1.2"
  }
```

Pull request description:
Support Babel/Typescript "faux modules" as well as native ESM
Fixes #935

Patch:
diff --git a/package.json b/package.json
index 8fd4e11..23f8f58 100644
--- a/package.json
+++ b/package.json
@@ -18,8 +18,8 @@
   "scripts": {
     "docs": "docsify serve",
     "browser-test": "airtap --local 8080 test/browser*test.js",
-    "test": "standard | snazzy && tap --100 test/*test.js",
-    "cov-ui": "tap --coverage-report=html test/*test.js",
+    "test": "standard | snazzy && tap --100 test/*test.js test/*/*test.js",
+    "cov-ui": "tap --coverage-report=html test/*test.js test/*/*test.js",
     "bench": "node benchmarks/utils/runbench all",
     "bench-basic": "node benchmarks/utils/runbench basic",
     "bench-object": "node benchmarks/utils/runbench object",
diff --git a/pino.js b/pino.js
index 5052109..4b8bdb8 100644
--- a/pino.js
+++ b/pino.js
@@ -195,14 +195,17 @@ function levelNameLabelFormatter (name) {
   }
 }
 
-pino.extreme = (dest = process.stdout.fd) => {
+module.exports = pino
+
+module.exports.extreme = (dest = process.stdout.fd) => {
   process.emitWarning(
     'The pino.extreme() option is deprecated and will be removed in v7. Use pino.destination({ sync: false }) instead.',
     { code: 'extreme_deprecation' }
   )
   return buildSafeSonicBoom({ dest, minLength: 4096, sync: false })
 }
-pino.destination = (dest = process.stdout.fd) => {
+
+module.exports.destination = (dest = process.stdout.fd) => {
   if (typeof dest === 'object') {
     dest.dest = dest.dest || process.stdout.fd
     return buildSafeSonicBoom(dest)
@@ -211,11 +214,13 @@ pino.destination = (dest = process.stdout.fd) => {
   }
 }
 
-pino.final = final
-pino.levels = mappings()
-pino.stdSerializers = serializers
-pino.stdTimeFunctions = Object.assign({}, time)
-pino.symbols = symbols
-pino.version = version
+module.exports.final = final
+module.exports.levels = mappings()
+module.exports.stdSerializers = serializers
+module.exports.stdTimeFunctions = Object.assign({}, time)
+module.exports.symbols = symbols
+module.exports.version = version
 
-module.exports = pino
+// Enables default and name export with TypeScript and Babel
+module.exports.default = pino
+module.exports.pino = pino


Test patch:
diff --git a/test/basic.test.js b/test/basic.test.js
index fa29c33..5625f14 100644
--- a/test/basic.test.js
+++ b/test/basic.test.js
@@ -1,31 +1,13 @@
 'use strict'
 const os = require('os')
 const { join } = require('path')
-const { readFileSync, existsSync, statSync } = require('fs')
+const { readFileSync } = require('fs')
 const { test } = require('tap')
-const { sink, check, once } = require('./helper')
+const { sink, check, once, watchFileCreated } = require('./helper')
 const pino = require('../')
 const { version } = require('../package.json')
 const { pid } = process
 const hostname = os.hostname()
-const watchFileCreated = (filename) => new Promise((resolve, reject) => {
-  const TIMEOUT = 800
-  const INTERVAL = 100
-  const threshold = TIMEOUT / INTERVAL
-  let counter = 0
-  const interval = setInterval(() => {
-    // On some CI runs file is created but not filled
-    if (existsSync(filename) && statSync(filename).size !== 0) {
-      clearInterval(interval)
-      resolve()
-    } else if (counter <= threshold) {
-      counter++
-    } else {
-      clearInterval(interval)
-      reject(new Error(`${filename} was not created.`))
-    }
-  }, INTERVAL)
-})
 
 test('pino version is exposed on export', async ({ is }) => {
   is(pino.version, version)
@@ -681,3 +663,12 @@ test('correctly log NaN', async (t) => {
   const { num } = await once(stream, 'data')
   t.is(num, null)
 })
+
+test('offers a .default() method to please typescript', async ({ is }) => {
+  is(pino.default, pino)
+
+  const stream = sink()
+  const instance = pino.default(stream)
+  instance.info('hello world')
+  check(is, await once(stream, 'data'), 30, 'hello world')
+})
diff --git a/test/esm/esm.mjs b/test/esm/esm.mjs
new file mode 100644
index 0000000..812b50d
--- /dev/null
+++ b/test/esm/esm.mjs
@@ -0,0 +1,12 @@
+import t from 'tap'
+import pino from '../../pino.js'
+import helper from '../helper.js'
+
+const { sink, check, once } = helper
+
+t.test('esm support', async ({ is }) => {
+  const stream = sink()
+  const instance = pino(stream)
+  instance.info('hello world')
+  check(is, await once(stream, 'data'), 30, 'hello world')
+})
diff --git a/test/esm/index.test.js b/test/esm/index.test.js
new file mode 100644
index 0000000..0d1a96c
--- /dev/null
+++ b/test/esm/index.test.js
@@ -0,0 +1,32 @@
+'use strict'
+
+const t = require('tap')
+const semver = require('semver')
+
+if (!semver.satisfies(process.versions.node, '^13.3.0 || ^12.10.0 || >= 14.0.0')) {
+  t.skip('Skip esm because not supported by Node')
+} else {
+  // Node v8 throw a `SyntaxError: Unexpected token import`
+  // even if this branch is never touch in the code,
+  // by using `eval` we can avoid this issue.
+  // eslint-disable-next-line
+  new Function('module', 'return import(module)')('./esm.mjs').catch((err) => {
+    process.nextTick(() => {
+      throw err
+    })
+  })
+}
+
+if (!semver.satisfies(process.versions.node, '>= 14.13.0 || ^12.20.0')) {
+  t.skip('Skip named exports because not supported by Node')
+} else {
+  // Node v8 throw a `SyntaxError: Unexpected token import`
+  // even if this branch is never touch in the code,
+  // by using `eval` we can avoid this issue.
+  // eslint-disable-next-line
+  new Function('module', 'return import(module)')('./named-exports.mjs').catch((err) => {
+    process.nextTick(() => {
+      throw err
+    })
+  })
+}
diff --git a/test/esm/named-exports.mjs b/test/esm/named-exports.mjs
new file mode 100644
index 0000000..36b38de
--- /dev/null
+++ b/test/esm/named-exports.mjs
@@ -0,0 +1,31 @@
+import { tmpdir, hostname } from 'os'
+import t from 'tap'
+import { sink, check, once, watchFileCreated } from '../helper.js'
+import { pino, destination } from '../../pino.js'
+import { join } from 'path'
+import { readFileSync } from 'fs'
+
+t.test('named exports support', async ({ is }) => {
+  const stream = sink()
+  const instance = pino(stream)
+  instance.info('hello world')
+  check(is, await once(stream, 'data'), 30, 'hello world')
+})
+
+t.test('destination', async ({ same }) => {
+  const tmp = join(
+    tmpdir(),
+    '_' + Math.random().toString(36).substr(2, 9)
+  )
+  const instance = pino(destination(tmp))
+  instance.info('hello')
+  await watchFileCreated(tmp)
+  const result = JSON.parse(readFileSync(tmp).toString())
+  delete result.time
+  same(result, {
+    pid: process.pid,
+    hostname,
+    level: 30,
+    msg: 'hello'
+  })
+})
diff --git a/test/helper.js b/test/helper.js
index 94fa6bb..dd25d9d 100644
--- a/test/helper.js
+++ b/test/helper.js
@@ -3,6 +3,7 @@
 const os = require('os')
 const writer = require('flush-write-stream')
 const split = require('split2')
+const { existsSync, statSync } = require('fs')
 const pid = process.pid
 const hostname = os.hostname()
 
@@ -50,4 +51,25 @@ function sleep (ms) {
   })
 }
 
-module.exports = { getPathToNull, sink, check, once, sleep }
+function watchFileCreated (filename) {
+  return new Promise((resolve, reject) => {
+    const TIMEOUT = 800
+    const INTERVAL = 100
+    const threshold = TIMEOUT / INTERVAL
+    let counter = 0
+    const interval = setInterval(() => {
+      // On some CI runs file is created but not filled
+      if (existsSync(filename) && statSync(filename).size !== 0) {
+        clearInterval(interval)
+        resolve()
+      } else if (counter <= threshold) {
+        counter++
+      } else {
+        clearInterval(interval)
+        reject(new Error(`${filename} was not created.`))
+      }
+    }, INTERVAL)
+  })
+}
+
+module.exports = { getPathToNull, sink, check, once, sleep, watchFileCreated }