From 0788652e083e57b29be66e28b94afdbecc9782a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Israel=20Ortiz=20Garc=C3=ADa?= Date: Thu, 28 Sep 2023 18:09:28 -0700 Subject: [PATCH 01/11] Add 'isNodeJs' getter to cli_pkg/js.dart --- lib/js.dart | 15 +++++++++++++++ test/npm_test.dart | 22 ++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/lib/js.dart b/lib/js.dart index 3d75ca2..df42a7d 100644 --- a/lib/js.dart +++ b/lib/js.dart @@ -12,7 +12,22 @@ // See the License for the specific language governing permissions and // limitations under the License. +import 'package:js/js.dart'; import 'package:js/js_util.dart'; +import 'package:node_interop/process.dart'; + +@JS('process') +external final Process? _process; // process is null in the browser + +/// This extension adds `maybe` getters that return non-nullable +/// properties with a nullable type. +extension PartialProcess on Process { + /// Returns [release] as nullable. + Release? get maybeRelease => release; +} + +/// Whether the code is being executed in a NodeJS environment. +bool get isNodeJs => _process?.maybeRelease?.name == 'node'; /// Whether this Dart code is running in a strict mode context. /// diff --git a/test/npm_test.dart b/test/npm_test.dart index 80bf7af..320d642 100644 --- a/test/npm_test.dart +++ b/test/npm_test.dart @@ -1135,6 +1135,28 @@ void main() { test("handles a thrown undefined", () => assertCatchesGracefully('BigInt(undefined)')); }); + + test("isNodeJs returns `true` when running in NodeJS", () async { + await d.package(pubspec, _enableNpm, [ + _packageJson, + d.dir("bin", [ + d.file("foo.dart", """ + import 'package:cli_pkg/js.dart'; + + void main() { + print(isNodeJs); + } + """) + ]), + ]).create(); + + await (await grind(["pkg-npm-dev"])).shouldExit(); + + var process = await TestProcess.start( + "node$dotExe", [d.path("my_app/build/npm/foo.js")]); + expect(process.stdout, emitsInOrder(["true", emitsDone])); + expect(process.shouldExit(0), completes); + }); }); } From abd9488b2a21c7056688ca9aeaed9f0ea9223ac8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Israel=20Ortiz=20Garc=C3=ADa?= Date: Thu, 28 Sep 2023 18:27:43 -0700 Subject: [PATCH 02/11] Add the 'process' getter to cli_pkg/js.dart --- CHANGELOG.md | 4 ++++ lib/js.dart | 15 ++++++++++++++- pubspec.yaml | 2 +- test/npm_test.dart | 22 ++++++++++++++++++++++ 4 files changed, 41 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e3e502..df8483e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.6.0 + +* Add `isNodeJs` and `process` getters to `package:cli_pkg/js.dart`. + ## 2.5.0 * Add a `wrapJSException()` function in the new `package:cli_pkg/js.dart` diff --git a/lib/js.dart b/lib/js.dart index df42a7d..ae6023f 100644 --- a/lib/js.dart +++ b/lib/js.dart @@ -26,9 +26,22 @@ extension PartialProcess on Process { Release? get maybeRelease => release; } -/// Whether the code is being executed in a NodeJS environment. +/// Whether the script is being executed in a NodeJS environment. bool get isNodeJs => _process?.maybeRelease?.name == 'node'; +/// Returns the NodeJs [Process] value only when the script is running in +/// NodeJS, otherwise returns `null`. +/// +/// By checking whether the script is running in NodeJS we can avoid returning a +/// non-[Process] object if the script is running in a browser, and there is a +/// different `process` object in the `window`. +/// +/// The getter can still return a non-[Process] object if the script is running +/// in a browser and there is a `process` object in the `window` with the value +/// `{release: {name: 'node'}}`. In this case the script must trust the browser +/// is emulating a NodeJS environment. +Process? get process => isNodeJs ? _process : null; + /// Whether this Dart code is running in a strict mode context. /// /// In strict mode, assigning properties to primitive types is an error. This diff --git a/pubspec.yaml b/pubspec.yaml index b81ed79..61384a8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: cli_pkg -version: 2.5.0 +version: 2.6.0 description: Grinder tasks for releasing Dart CLI packages. homepage: https://github.com/google/dart_cli_pkg diff --git a/test/npm_test.dart b/test/npm_test.dart index 320d642..a4752b5 100644 --- a/test/npm_test.dart +++ b/test/npm_test.dart @@ -1157,6 +1157,28 @@ void main() { expect(process.stdout, emitsInOrder(["true", emitsDone])); expect(process.shouldExit(0), completes); }); + + test("process returns a Process when running in NodeJS", () async { + await d.package(pubspec, _enableNpm, [ + _packageJson, + d.dir("bin", [ + d.file("foo.dart", """ + import 'package:cli_pkg/js.dart'; + + void main() { + print(process?.release.name); + } + """) + ]), + ]).create(); + + await (await grind(["pkg-npm-dev"])).shouldExit(); + + var process = await TestProcess.start( + "node$dotExe", [d.path("my_app/build/npm/foo.js")]); + expect(process.stdout, emitsInOrder(["node", emitsDone])); + expect(process.shouldExit(0), completes); + }); }); } From 834432247b7e18397414a08964dacbbc1003aa75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Israel=20Ortiz=20Garc=C3=ADa?= Date: Fri, 29 Sep 2023 17:30:34 -0700 Subject: [PATCH 03/11] Add browser tests, oops didn't realize dart made testing browsers very easy --- browser_library_test/test/node_js_test.dart | 79 +++++++++++++++++++++ lib/js.dart | 10 +-- test/npm_test.dart | 4 +- 3 files changed, 86 insertions(+), 7 deletions(-) create mode 100644 browser_library_test/test/node_js_test.dart diff --git a/browser_library_test/test/node_js_test.dart b/browser_library_test/test/node_js_test.dart new file mode 100644 index 0000000..ecc5e7a --- /dev/null +++ b/browser_library_test/test/node_js_test.dart @@ -0,0 +1,79 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'dart:js_interop'; +import 'dart:js_interop_unsafe'; +import 'dart:js_util'; + +import 'package:cli_pkg/js.dart'; +import 'package:test/test.dart'; + +@TestOn('browser') +void main() { + tearDown(() => globalThis.toJS.delete('process'.toJS)); + + const nonNodeJsProcessTestCases = >>{ + 'an empty process': {}, + 'a process with empty release': {'release': {}}, + 'a process with non-Node.JS release name': { + 'release': {'name': 'hello'} + }, + }; + + const fakeNodeJsProcess = { + 'release': {'name': 'node'} + }; + + group('isNodeJs', () { + test("returns 'false' from the browser", () { + expect(isNodeJs, isFalse); + }); + + for (final entry in nonNodeJsProcessTestCases.entries) { + final caseName = entry.key; + final processJson = entry.value.jsify(); + + test("returns 'false' when $caseName exists in the 'window'", () { + globalThis.toJS['process'.toJS] = processJson; + expect(isNodeJs, isFalse); + }); + } + + test("returns 'true' with a fake Node.JS process", () { + globalThis.toJS['process'.toJS] = fakeNodeJsProcess.jsify(); + expect(isNodeJs, isTrue); + }); + }); + + group('process', () { + test("returns 'null' from the browser", () { + expect(process, isNull); + }); + + for (final entry in nonNodeJsProcessTestCases.entries) { + final caseName = entry.key; + final processJson = entry.value.jsify(); + + test("returns 'null' when $caseName exists in the 'window'", () { + globalThis.toJS['process'.toJS] = processJson; + expect(process, isNull); + }); + } + + test("returns a fake process if it fakes being a Node.JS environment", () { + globalThis.toJS['process'.toJS] = fakeNodeJsProcess.jsify(); + expect(process.jsify().dartify(), fakeNodeJsProcess); + }); + }); +} diff --git a/lib/js.dart b/lib/js.dart index ae6023f..3688320 100644 --- a/lib/js.dart +++ b/lib/js.dart @@ -26,20 +26,20 @@ extension PartialProcess on Process { Release? get maybeRelease => release; } -/// Whether the script is being executed in a NodeJS environment. +/// Whether the script is being executed in a Node.JS environment. bool get isNodeJs => _process?.maybeRelease?.name == 'node'; /// Returns the NodeJs [Process] value only when the script is running in -/// NodeJS, otherwise returns `null`. +/// Node.JS, otherwise returns `null`. /// -/// By checking whether the script is running in NodeJS we can avoid returning a -/// non-[Process] object if the script is running in a browser, and there is a +/// By checking whether the script is running in Node.JS we can avoid returning +/// a non-[Process] object if the script is running in a browser, and there is a /// different `process` object in the `window`. /// /// The getter can still return a non-[Process] object if the script is running /// in a browser and there is a `process` object in the `window` with the value /// `{release: {name: 'node'}}`. In this case the script must trust the browser -/// is emulating a NodeJS environment. +/// is emulating a Node.JS environment. Process? get process => isNodeJs ? _process : null; /// Whether this Dart code is running in a strict mode context. diff --git a/test/npm_test.dart b/test/npm_test.dart index a4752b5..5b4cecd 100644 --- a/test/npm_test.dart +++ b/test/npm_test.dart @@ -1136,7 +1136,7 @@ void main() { () => assertCatchesGracefully('BigInt(undefined)')); }); - test("isNodeJs returns `true` when running in NodeJS", () async { + test("isNodeJs returns `true` when running in Node.JS", () async { await d.package(pubspec, _enableNpm, [ _packageJson, d.dir("bin", [ @@ -1158,7 +1158,7 @@ void main() { expect(process.shouldExit(0), completes); }); - test("process returns a Process when running in NodeJS", () async { + test("process returns a Process when running in Node.JS", () async { await d.package(pubspec, _enableNpm, [ _packageJson, d.dir("bin", [ From 88fdbe47df3ffaa6bad60b945ac0928be19d4615 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Israel=20Ortiz=20Garc=C3=ADa?= Date: Fri, 29 Sep 2023 18:10:50 -0700 Subject: [PATCH 04/11] use .toJSBox for objects, .toJS is deleted in Dart Dev --- browser_library_test/test/node_js_test.dart | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/browser_library_test/test/node_js_test.dart b/browser_library_test/test/node_js_test.dart index ecc5e7a..f8f07a6 100644 --- a/browser_library_test/test/node_js_test.dart +++ b/browser_library_test/test/node_js_test.dart @@ -21,7 +21,7 @@ import 'package:test/test.dart'; @TestOn('browser') void main() { - tearDown(() => globalThis.toJS.delete('process'.toJS)); + tearDown(() => globalThis.toJSBox.delete('process'.toJS)); const nonNodeJsProcessTestCases = >>{ 'an empty process': {}, @@ -45,13 +45,13 @@ void main() { final processJson = entry.value.jsify(); test("returns 'false' when $caseName exists in the 'window'", () { - globalThis.toJS['process'.toJS] = processJson; + globalThis.toJSBox['process'.toJS] = processJson; expect(isNodeJs, isFalse); }); } test("returns 'true' with a fake Node.JS process", () { - globalThis.toJS['process'.toJS] = fakeNodeJsProcess.jsify(); + globalThis.toJSBox['process'.toJS] = fakeNodeJsProcess.jsify(); expect(isNodeJs, isTrue); }); }); @@ -66,13 +66,13 @@ void main() { final processJson = entry.value.jsify(); test("returns 'null' when $caseName exists in the 'window'", () { - globalThis.toJS['process'.toJS] = processJson; + globalThis.toJSBox['process'.toJS] = processJson; expect(process, isNull); }); } test("returns a fake process if it fakes being a Node.JS environment", () { - globalThis.toJS['process'.toJS] = fakeNodeJsProcess.jsify(); + globalThis.toJSBox['process'.toJS] = fakeNodeJsProcess.jsify(); expect(process.jsify().dartify(), fakeNodeJsProcess); }); }); From ea785a06384e8ef2de5b8629ddc76223d8d0c9c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Israel=20Ortiz=20Garc=C3=ADa?= Date: Fri, 29 Sep 2023 18:42:39 -0700 Subject: [PATCH 05/11] add example that uses process.env --- lib/js.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/js.dart b/lib/js.dart index 3688320..d286a8e 100644 --- a/lib/js.dart +++ b/lib/js.dart @@ -34,7 +34,11 @@ bool get isNodeJs => _process?.maybeRelease?.name == 'node'; /// /// By checking whether the script is running in Node.JS we can avoid returning /// a non-[Process] object if the script is running in a browser, and there is a -/// different `process` object in the `window`. +/// different `process` object in the `window`. This can happen when a library +/// or framework adds a global variable named `process`. For example, Next.JS +/// adds [`process.env`] to access environment variables on the browser. +/// +/// [`process.env`]: https://nextjs.org/docs/pages/building-your-application/configuring/environment-variables#bundling-environment-variables-for-the-browser /// /// The getter can still return a non-[Process] object if the script is running /// in a browser and there is a `process` object in the `window` with the value From f96010acfc6193872ea1015eaa4ddff23dd8c792 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Israel=20Ortiz=20Garc=C3=ADa?= Date: Fri, 29 Sep 2023 18:47:38 -0700 Subject: [PATCH 06/11] ok.. avoid using toJSBox too on globalThis --- browser_library_test/test/node_js_test.dart | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/browser_library_test/test/node_js_test.dart b/browser_library_test/test/node_js_test.dart index f8f07a6..6a811f0 100644 --- a/browser_library_test/test/node_js_test.dart +++ b/browser_library_test/test/node_js_test.dart @@ -13,7 +13,6 @@ // limitations under the License. import 'dart:js_interop'; -import 'dart:js_interop_unsafe'; import 'dart:js_util'; import 'package:cli_pkg/js.dart'; @@ -21,7 +20,7 @@ import 'package:test/test.dart'; @TestOn('browser') void main() { - tearDown(() => globalThis.toJSBox.delete('process'.toJS)); + tearDown(() => delete(globalThis, 'process')); const nonNodeJsProcessTestCases = >>{ 'an empty process': {}, @@ -45,13 +44,13 @@ void main() { final processJson = entry.value.jsify(); test("returns 'false' when $caseName exists in the 'window'", () { - globalThis.toJSBox['process'.toJS] = processJson; + setProperty(globalThis, 'process', processJson); expect(isNodeJs, isFalse); }); } test("returns 'true' with a fake Node.JS process", () { - globalThis.toJSBox['process'.toJS] = fakeNodeJsProcess.jsify(); + setProperty(globalThis, 'process', fakeNodeJsProcess.jsify()); expect(isNodeJs, isTrue); }); }); @@ -66,13 +65,13 @@ void main() { final processJson = entry.value.jsify(); test("returns 'null' when $caseName exists in the 'window'", () { - globalThis.toJSBox['process'.toJS] = processJson; + setProperty(globalThis, 'process', processJson); expect(process, isNull); }); } test("returns a fake process if it fakes being a Node.JS environment", () { - globalThis.toJSBox['process'.toJS] = fakeNodeJsProcess.jsify(); + setProperty(globalThis, 'process', fakeNodeJsProcess.jsify()); expect(process.jsify().dartify(), fakeNodeJsProcess); }); }); From e26df989e65b3fcf0771d5f67044d710afab0cc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Israel=20Ortiz=20Garc=C3=ADa?= Date: Tue, 3 Oct 2023 14:57:38 -0700 Subject: [PATCH 07/11] disable dart-dev CI testing due to https://github.com/dart-lang/sdk/issues/52121 --- .github/workflows/ci.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cb9c50b..29c03e9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,9 @@ jobs: strategy: matrix: os: [ubuntu, macos, windows] - dart-channel: [stable, dev] + # Testing dart-dev disabled due to: + # https://github.com/dart-lang/sdk/issues/52121 + dart-channel: [stable] shard: [0, 1, 2] fail-fast: false @@ -49,7 +51,9 @@ jobs: strategy: matrix: - dart-channel: [stable, dev] + # Testing dart-dev disabled due to: + # https://github.com/dart-lang/sdk/issues/52121 + dart-channel: [stable] fail-fast: false runs-on: ubuntu-latest From 7ca1ecdd5708324498abfcb0b0c9de66c11a0659 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Israel=20Ortiz=20Garc=C3=ADa?= Date: Tue, 3 Oct 2023 15:03:04 -0700 Subject: [PATCH 08/11] lint? --- browser_library_test/test/node_js_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/browser_library_test/test/node_js_test.dart b/browser_library_test/test/node_js_test.dart index 6a811f0..391449d 100644 --- a/browser_library_test/test/node_js_test.dart +++ b/browser_library_test/test/node_js_test.dart @@ -20,7 +20,7 @@ import 'package:test/test.dart'; @TestOn('browser') void main() { - tearDown(() => delete(globalThis, 'process')); + tearDown(() => delete(globalThis, 'process')); const nonNodeJsProcessTestCases = >>{ 'an empty process': {}, From 49ee9daea12de455bdfdd2f8fc784c893a7987a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Israel=20Ortiz=20Garc=C3=ADa?= Date: Tue, 3 Oct 2023 15:16:28 -0700 Subject: [PATCH 09/11] review --- .github/workflows/ci.yml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 29c03e9..b86845c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,9 +21,11 @@ jobs: strategy: matrix: os: [ubuntu, macos, windows] - # Testing dart-dev disabled due to: - # https://github.com/dart-lang/sdk/issues/52121 dart-channel: [stable] + # TODO(nweiz): Re-enable this when + # https://github.com/dart-lang/sdk/issues/52121#issuecomment-1728534228 + # is addressed. + # dart-channel: [stable, dev] shard: [0, 1, 2] fail-fast: false @@ -51,9 +53,11 @@ jobs: strategy: matrix: - # Testing dart-dev disabled due to: - # https://github.com/dart-lang/sdk/issues/52121 dart-channel: [stable] + # TODO(nweiz): Re-enable this when + # https://github.com/dart-lang/sdk/issues/52121#issuecomment-1728534228 + # is addressed. + # dart-channel: [stable, dev] fail-fast: false runs-on: ubuntu-latest From 9ece1f954fd3808526094d356ac9a996c2777ef1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Israel=20Ortiz=20Garc=C3=ADa?= Date: Tue, 3 Oct 2023 16:21:24 -0700 Subject: [PATCH 10/11] rename nodejs-in-browser test file --- browser_library_test/test/browser_test_shared.dart | 8 ++++++++ ..._js_test.dart => node_js_helpers_in_browser_test.dart} | 3 ++- lib/js.dart | 3 +++ 3 files changed, 13 insertions(+), 1 deletion(-) rename browser_library_test/test/{node_js_test.dart => node_js_helpers_in_browser_test.dart} (99%) diff --git a/browser_library_test/test/browser_test_shared.dart b/browser_library_test/test/browser_test_shared.dart index f45e8f7..d837168 100644 --- a/browser_library_test/test/browser_test_shared.dart +++ b/browser_library_test/test/browser_test_shared.dart @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +import 'package:cli_pkg/js.dart'; import 'package:js/js.dart'; import 'package:test/test.dart'; @@ -50,4 +51,11 @@ void main() { test("the default dependency is not loaded", () { expect(loadedDefaultDependency, isFalse); }); + + test("isNodeJs returns 'false'", () { + expect(isNodeJs, isFalse); + }); + test("process returns 'null'", () { + expect(process, isNull); + }); } diff --git a/browser_library_test/test/node_js_test.dart b/browser_library_test/test/node_js_helpers_in_browser_test.dart similarity index 99% rename from browser_library_test/test/node_js_test.dart rename to browser_library_test/test/node_js_helpers_in_browser_test.dart index 391449d..e858de8 100644 --- a/browser_library_test/test/node_js_test.dart +++ b/browser_library_test/test/node_js_helpers_in_browser_test.dart @@ -12,13 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. +@TestOn('browser') + import 'dart:js_interop'; import 'dart:js_util'; import 'package:cli_pkg/js.dart'; import 'package:test/test.dart'; -@TestOn('browser') void main() { tearDown(() => delete(globalThis, 'process')); diff --git a/lib/js.dart b/lib/js.dart index d286a8e..82509d0 100644 --- a/lib/js.dart +++ b/lib/js.dart @@ -44,6 +44,9 @@ bool get isNodeJs => _process?.maybeRelease?.name == 'node'; /// in a browser and there is a `process` object in the `window` with the value /// `{release: {name: 'node'}}`. In this case the script must trust the browser /// is emulating a Node.JS environment. +/// +/// Note: Add a dependency on `node_interop` to ensure you get a version +/// compatible with your usage. Process? get process => isNodeJs ? _process : null; /// Whether this Dart code is running in a strict mode context. From 2f42a2153cd1e1fb21e667a7b0d9d63bb6d79613 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Israel=20Ortiz=20Garc=C3=ADa?= Date: Tue, 3 Oct 2023 16:28:12 -0700 Subject: [PATCH 11/11] review --- browser_library_test/test/browser_test_shared.dart | 1 + lib/js.dart | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/browser_library_test/test/browser_test_shared.dart b/browser_library_test/test/browser_test_shared.dart index d837168..f8d6222 100644 --- a/browser_library_test/test/browser_test_shared.dart +++ b/browser_library_test/test/browser_test_shared.dart @@ -55,6 +55,7 @@ void main() { test("isNodeJs returns 'false'", () { expect(isNodeJs, isFalse); }); + test("process returns 'null'", () { expect(process, isNull); }); diff --git a/lib/js.dart b/lib/js.dart index 82509d0..ca380bb 100644 --- a/lib/js.dart +++ b/lib/js.dart @@ -45,8 +45,8 @@ bool get isNodeJs => _process?.maybeRelease?.name == 'node'; /// `{release: {name: 'node'}}`. In this case the script must trust the browser /// is emulating a Node.JS environment. /// -/// Note: Add a dependency on `node_interop` to ensure you get a version -/// compatible with your usage. +/// Note: If you use this, add a dependency on `node_interop` to ensure you get +/// a version compatible with your usage. Process? get process => isNodeJs ? _process : null; /// Whether this Dart code is running in a strict mode context.