Skip to content

Commit 802ca41

Browse files
nex3jathak
andauthored
Add package metadata files and get tests running (#1)
Co-authored-by: Jennifer Thakar <[email protected]>
1 parent 7b29467 commit 802ca41

16 files changed

+403
-4
lines changed

.eslintignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
build/
2+
dist/
3+
**/*.js

.eslintrc

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"extends": "./node_modules/gts/",
3+
"rules": {
4+
"@typescript-eslint/explicit-function-return-type": [
5+
"error",
6+
{"allowExpressions": true}
7+
],
8+
"func-style": ["error", "declaration"],
9+
"prefer-const": ["error", {"destructuring": "all"}],
10+
// It would be nice to sort import declaration order as well, but that's not
11+
// autofixable and it's not worth the effort of handling manually.
12+
"sort-imports": ["error", {"ignoreDeclarationSort": true}],
13+
}
14+
}

.github/dependabot.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: "npm"
4+
directory: "/"
5+
schedule:
6+
interval: "weekly"
7+
- package-ecosystem: "github-actions"
8+
directory: "/"
9+
schedule:
10+
interval: "weekly"

.github/workflows/ci.yml

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
name: CI
2+
3+
defaults:
4+
run: {shell: bash}
5+
6+
env:
7+
PROTOC_VERSION: 3.x
8+
9+
on:
10+
push:
11+
branches: [main, feature.*]
12+
tags: ['**']
13+
pull_request:
14+
15+
jobs:
16+
static_analysis:
17+
name: Static analysis
18+
runs-on: ubuntu-latest
19+
20+
steps:
21+
- uses: actions/checkout@v4
22+
- uses: actions/setup-node@v4
23+
with:
24+
node-version: 'lts/*'
25+
check-latest: true
26+
- run: npm install
27+
- run: npm run check
28+
29+
tests:
30+
name: 'Tests | Node ${{ matrix.node-version }} | ${{ matrix.os }}'
31+
runs-on: ${{ matrix.os }}-latest
32+
33+
strategy:
34+
matrix:
35+
os: [ubuntu, macos, windows]
36+
node-version: ['lts/*', 'lts/-1', 'lts/-2']
37+
fail-fast: false
38+
39+
steps:
40+
- uses: actions/checkout@v4
41+
- uses: actions/setup-node@v4
42+
with:
43+
node-version: ${{ matrix.node-version }}
44+
check-latest: true
45+
- run: npm install
46+
- run: npm run test
47+
48+
deploy:
49+
name: Deploy
50+
runs-on: ubuntu-latest
51+
if: "startsWith(github.ref, 'refs/tags/') && github.repository == 'sass/sync-message-channel'"
52+
needs: [static_analysis, tests, sass_spec]
53+
54+
steps:
55+
- uses: actions/checkout@v4
56+
- uses: actions/setup-node@v4
57+
with:
58+
node-version: 'lts/*'
59+
check-latest: true
60+
registry-url: 'https://registry.npmjs.org'
61+
- run: npm install
62+
- run: npm publish
63+
env:
64+
NODE_AUTH_TOKEN: '${{ secrets.NPM_TOKEN }}'
65+
66+
typedoc:
67+
runs-on: ubuntu-latest
68+
if: "startsWith(github.ref, 'refs/tags/') && github.repository == 'sass/sync-message-channel'"
69+
needs: [deploy]
70+
71+
environment:
72+
name: github-pages
73+
url: ${{ steps.deployment.outputs.page_url }}
74+
75+
permissions:
76+
pages: write
77+
id-token: write
78+
79+
steps:
80+
- uses: actions/checkout@v4
81+
- uses: actions/setup-node@v4
82+
with:
83+
node-version: 'lts/*'
84+
check-latest: true
85+
registry-url: 'https://registry.npmjs.org'
86+
- run: npm install
87+
- run: npm run doc
88+
89+
- name: Upload static files as artifact
90+
uses: actions/upload-pages-artifact@v3
91+
with: {path: docs}
92+
93+
- id: deployment
94+
uses: actions/deploy-pages@v4

.gitignore

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
.DS_Store
2+
build
3+
dist
4+
node_modules
5+
npm-debug.log*
6+
package-lock.json
7+
8+
# Editors
9+
.idea
10+
.vscode
11+
*.njsproj
12+
*.ntvs*
13+
*.sln
14+
*.suo
15+
*.sw?

.prettierrc.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module.exports = {
2+
...require('gts/.prettierrc.json'),
3+
};

CODE_OF_CONDUCT.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
Sass is more than a technology; Sass is driven by the community of individuals
2+
that power its development and use every day. As a community, we want to embrace
3+
the very differences that have made our collaboration so powerful, and work
4+
together to provide the best environment for learning, growing, and sharing of
5+
ideas. It is imperative that we keep Sass a fun, welcoming, challenging, and
6+
fair place to play.
7+
8+
[The full community guidelines can be found on the Sass website.][link]
9+
10+
[link]: http://sass-lang.com/community-guidelines

CONTRIBUTING.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# How to Contribute
2+
3+
We'd love to accept your patches and contributions to this project. There are
4+
just a few small guidelines you need to follow.
5+
6+
* [Contributor License Agreement](#contributor-license-agreement)
7+
* [Code Reviews](#code-reviews)
8+
* [Large Language Models](#large-language-models)
9+
10+
## Contributor License Agreement
11+
12+
Contributions to this project must be accompanied by a Contributor License
13+
Agreement. You (or your employer) retain the copyright to your contribution;
14+
this simply gives us permission to use and redistribute your contributions as
15+
part of the project. Head over to <https://cla.developers.google.com/> to see
16+
your current agreements on file or to sign a new one.
17+
18+
You generally only need to submit a CLA once, so if you've already submitted one
19+
(even if it was for a different project), you probably don't need to do it
20+
again.
21+
22+
## Code Reviews
23+
24+
All submissions, including submissions by project members, require review. We
25+
use GitHub pull requests for this purpose. Consult
26+
[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
27+
information on using pull requests.
28+
29+
## Large Language Models
30+
31+
Do not submit any code or prose written or modified by large language models or
32+
"artificial intelligence" such as GitHub Copilot or ChatGPT to this project.
33+
These tools produce code that looks plausible, which means that not only is it
34+
likely to contain bugs those bugs are likely to be difficult to notice on
35+
review. In addition, because these models were trained indiscriminately and
36+
non-consensually on open-source code with a variety of licenses, it's not
37+
obvious that we have the moral or legal right to redistribute code they
38+
generate.

LICENSE

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
Copyright (c) 2024, Google LLC
2+
3+
Permission is hereby granted, free of charge, to any person obtaining
4+
a copy of this software and associated documentation files (the
5+
"Software"), to deal in the Software without restriction, including
6+
without limitation the rights to use, copy, modify, merge, publish,
7+
distribute, sublicense, and/or sell copies of the Software, and to
8+
permit persons to whom the Software is furnished to do so, subject to
9+
the following conditions:
10+
11+
The above copyright notice and this permission notice shall be
12+
included in all copies or substantial portions of the Software.
13+
14+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

README.md

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
# `sync-message-port`
2+
3+
This package exposes a utility class that encapsulates the ability to send and
4+
receive messages with arbitrary structure across Node.js worker boundaries. It
5+
can be used as the building block for synchronous versions of APIs that are
6+
traditionally only available asynchronously in the Node.js ecosystem by running
7+
the asynchronous APIs in a worker and accessing their results synchronously from
8+
the main thread.
9+
10+
See [the `sync-process` package] for an example of `sync-message-channel` in
11+
action.
12+
13+
[the `sync-process` package]: https://github.com/sass/sync-process
14+
15+
## Usage
16+
17+
1. Use `SyncMessagePort.createChanenl()` to create a message channel that's set
18+
up to be compatible with `SyncMessagePort`s. A normal `MessageChannel` won't
19+
work!
20+
21+
2. You can send this `MessageChannel`'s ports across worker boundaries just like
22+
any other `MessagePort`. Send one to the worker you want to communicate with
23+
synchronously.
24+
25+
3. Once you're ready to start sending and receiving messages, wrap *both* ports
26+
in `new SyncMessagePort()`, even if one is only ever going to be sending
27+
messages and not receiving them.
28+
29+
4. Use `SyncMessagePort.postMessage()` to send messages and
30+
`SyncMessagePort.receiveMessage()` to receive them synchronously.
31+
32+
```js
33+
import {Worker} from 'node:worker_threads';
34+
import {SyncMessagePort} from 'sync-message-channel';
35+
// or
36+
// const {SyncMessagePort} = require('sync-message-port');
37+
38+
// Channels must be created using this function. A MessageChannel created by
39+
// hand won't work.
40+
const channel = SyncMessagePort.createChannel();
41+
const localPort = new SyncMessagePort(channel.port1);
42+
43+
const worker = new Worker(`
44+
import {workerData} = require('node:worker_threads');
45+
import {SyncMessagePort} from 'sync-message-channel';
46+
47+
const remotePort = new SyncMessagePort(workerData.port);
48+
49+
setTimeout(() => {
50+
remotePort.postMessage("hello from worker!");
51+
}, 2000);
52+
`, {
53+
workerData: {port: channel.port2},
54+
transferList: [channel.port2],
55+
eval: true,
56+
});
57+
58+
// Note that because workers report errors asynchronously, this won't report an
59+
// error if the worker fails to load because the main thread will be
60+
// synchronously waiting for its first message.
61+
worker.on('error', console.error);
62+
63+
console.log(localPort.receiveMessage());
64+
```
65+
66+
## Why synchrony?
67+
68+
Although JavaScript in general and Node.js in particular are typically designed
69+
to embrace asynchrony, there are a number of reasons why a synchronous API may
70+
be preferable or even necessary.
71+
72+
### No a/synchronous polymorphism
73+
74+
Although `async`/`await` and the `Promise` API has substantially improved the
75+
usability of writing asynchronous code in JavaScript, it doesn't address one
76+
core issue: there's no way to write code that's *polymorphic* over asynchrony.
77+
Put in simpler terms, there's no language-level way to write a complex function
78+
that takes a callback and to run that functions synchronously if the callback is
79+
synchronous and asynchronously otherwise. The only option is to write the
80+
function twice.
81+
82+
This poses a real, practical problem when interacting with libraries. Suppose
83+
you have a library that takes a callback option—for example, an HTML
84+
sanitization library that takes a callback to determine how to handle a given
85+
`<a href="...">`. The library doesn't need to do any IO itself, so it's written
86+
synchronously. But what if your callback wants to make an HTTP request to
87+
determine how to handle a tag? You're stuck unless you can make that request
88+
synchronous. This library makes that possible.
89+
90+
### Performance considerations
91+
92+
Asynchrony is generally more performant in situations where there's a large
93+
amount of concurrent IO happening. But when performance is CPU-bound, it's often
94+
substantially worse due to the overhead of bouncing back and forth between the
95+
event loop and user code.
96+
97+
As a real-world example, the Sass compiler API supports both synchronous and
98+
asynchronous code paths to work around the polymorphism problem described above.
99+
The logic of these paths is exactly the same—the only difference is that the
100+
asynchronous path's functions all return `Promise`s instead of synchronous
101+
values. Compiling with the asynchronous path often takes 2-3x longer than with
102+
the synchronous path. This means that being able to run plugins synchronously
103+
can provide a substantial overall performance gain, even if the plugins
104+
themselves lose the benefit of concurrency.
105+
106+
## How does it work?
107+
108+
This uses [`Atomics`] and [`SharedArrayBuffer`] under the covers to signal
109+
across threads when messages are available, and
110+
[`worker_threads.receiveMessageOnPort()`] to actually retrieve messages.
111+
112+
[`Atomics`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics
113+
[`SharedArrayBuffer`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer
114+
[`Worker.receiveMessageOnPort()`]: https://nodejs.org/api/worker_threads.html#workerreceivemessageonportport
115+
116+
### Can I use this in a browser?
117+
118+
Unfortunately, no. Browsers don't support any equivalent of
119+
`worker_threads.receiveMessageOnPort()`, even within worker threads. You could
120+
make a similar package that can transmit only binary data (or data that can be
121+
encoded as binary) using only `SharedArrayBuffer`, but that's outside the scope
122+
of this package.
123+
124+
Disclaimer: this is not an official Google product.

0 commit comments

Comments
 (0)