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

v4: modernize (ES & ESM), overhaul code generation #134

Merged
merged 11 commits into from
Jul 8, 2024
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/node.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,5 @@ jobs:
- name: Run tests
run: npm test

- name: Run builds
run: npm run prepublishOnly
- name: Run build
run: npm run build
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ coverage
dist
*.log
node_modules
index.d.ts
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Copyright (c) 2017, Mapbox
Copyright (c) 2024, Mapbox
All rights reserved.

Redistribution and use in source and binary forms, with or without
Expand Down
90 changes: 41 additions & 49 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,38 +33,26 @@ $ pbf example.proto > example.js
Then read and write objects using the module like this:

```js
var Pbf = require('pbf');
var Example = require('./example.js').Example;
import Pbf from 'pbf';
import {readExample, writeExample} from './example.js';

// read
var pbf = new Pbf(buffer);
var obj = Example.read(pbf);
var obj = readExample(new Pbf(buffer));

// write
var pbf = new Pbf();
Example.write(obj, pbf);
var buffer = pbf.finish();
```

Alternatively, you can compile a module directly in the code:

```js
var compile = require('pbf/compile');
var schema = require('protocol-buffers-schema');

var proto = schema.parse(fs.readFileSync('example.proto'));
var Test = compile(proto).Test;
const pbf = new Pbf();
writeExample(obj, pbf);
const buffer = pbf.finish();
```

If you use `webpack` as your module bundler, you can use [pbf-loader](https://github.com/trivago/pbf-loader)
to load .proto files directly. It returns a compiled module ready to be used.
Alternatively, you can compile a protobuf schema file directly in the code:

Given you already configured your `webpack.config.js`, the code above would look like:
```js
var Pbf = require('pbf');
var proto = require('./example.proto');
import {compile} from 'pbf/compile';
import schema from 'protocol-buffers-schema';

var Test = proto.Test;
const proto = schema.parse(fs.readFileSync('example.proto'));
const {readExample, writeExample} = compile(proto);
```

#### Custom Reading
Expand Down Expand Up @@ -103,32 +91,36 @@ function writeLayer(layer, pbf) {

## Install

Node and Browserify:
Install using NPM with `npm install pbf`, then import as a module:

```bash
npm install pbf
```js
import Pbf from 'pbf';
```

Making a browser build:
Or use as a module directly in the browser with [jsDelivr](https://www.jsdelivr.com/esm):

```bash
npm install
npm run build-dev # dist/pbf-dev.js (development build)
npm run build-min # dist/pbf.js (minified production build)
```html
<script type="module">
import Pbf from 'https://cdn.jsdelivr.net/npm/pbf/+esm';
</script>
```

CDN link: https://unpkg.com/[email protected]/dist/pbf.js
Alternatively, there's a browser bundle with a `Pbf` global variable:

```html
<script src="https://cdn.jsdelivr.net/npm/pbf"></script>
```

## API

Create a `Pbf` object, optionally given a `Buffer` or `Uint8Array` as input data:

```js
// parse a pbf file from disk in Node
var pbf = new Pbf(fs.readFileSync('data.pbf'));
const pbf = new Pbf(fs.readFileSync('data.pbf'));

// parse a pbf file in a browser after an ajax request with responseType="arraybuffer"
var pbf = new Pbf(new Uint8Array(xhr.response));
const pbf = new Pbf(new Uint8Array(xhr.response));
```

`Pbf` object properties:
Expand All @@ -143,7 +135,7 @@ pbf.pos; // current offset for reading or writing
Read a sequence of fields:

```js
pbf.readFields(function (tag) {
pbf.readFields((tag) => {
if (tag === 1) pbf.readVarint();
else if (tag === 2) pbf.readString();
else ...
Expand All @@ -154,9 +146,9 @@ It optionally accepts an object that will be passed to the reading function for
and also passes the `Pbf` object as a third argument:

```js
var result = pbf.readFields(callback, {})
const result = pbf.readFields(readField, {})

function callback(tag, result, pbf) {
function readField(tag, result, pbf) {
if (tag === 1) result.id = pbf.readVarint();
}
```
Expand All @@ -166,17 +158,17 @@ To read an embedded message, use `pbf.readMessage(fn[, obj])` (in the same way a
Read values:

```js
var value = pbf.readVarint();
var str = pbf.readString();
var numbers = pbf.readPackedVarint();
const value = pbf.readVarint();
const str = pbf.readString();
const numbers = pbf.readPackedVarint();
```

For lazy or partial decoding, simply save the position instead of reading a value,
then later set it back to the saved value and read:

```js
var fooPos = -1;
pbf.readFields(function (tag) {
const fooPos = -1;
pbf.readFields((tag) => {
if (tag === 1) fooPos = pbf.pos;
});
...
Expand Down Expand Up @@ -277,7 +269,6 @@ Misc methods:
* `realloc(minBytes)` - pad the underlying buffer size to accommodate the given number of bytes;
note that the size increases exponentially, so it won't necessarily equal the size of data written
* `finish()` - make the current buffer ready for reading and return the data as a buffer slice
* `destroy()` - dispose the buffer

For an example of a real-world usage of the library, see [vector-tile-js](https://github.com/mapbox/vector-tile-js).

Expand All @@ -287,15 +278,16 @@ For an example of a real-world usage of the library, see [vector-tile-js](https:
If installed globally, `pbf` provides a binary that compiles `proto` files into JavaScript modules. Usage:

```bash
$ pbf <proto_path> [--no-write] [--no-read] [--browser]
$ pbf <proto_path> [--no-write] [--no-read] [--legacy]
```

The `--no-write` and `--no-read` switches remove corresponding code in the output.
The `--browser` switch makes the module work in browsers instead of Node.
The `--legacy` switch makes it generate a CommonJS module instead of ESM.

`Pbf` will generate `read<Identifier>` and `write<Identifier>` functions for every message in the schema. For nested messages, their names will be concatenated — e.g. `Message` inside `Test` will produce `readTestMessage` and `writeTestMessage` functions.

The resulting module exports each message by name with the following methods:

* `read(pbf)` - decodes an object from the given `Pbf` instance
* `write(obj, pbf)` - encodes an object into the given `Pbf` instance (usually empty)
* `read(pbf)` - decodes an object from the given `Pbf` instance.
* `write(obj, pbf)` - encodes an object into the given `Pbf` instance (usually empty).

The resulting code is clean and simple, so feel free to customize it.
The resulting code is clean and simple, so it's meant to be customized.
33 changes: 16 additions & 17 deletions bench/bench-tiles.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
'use strict';

var runStats = require('tile-stats-runner');
var Tile = require('./vector_tile').Tile;
var Pbf = require('../');
import runStats from 'tile-stats-runner';
import {readTile, writeTile} from './vector_tile.js';
import Pbf from '../index.js';

var ids = 'mapbox.mapbox-streets-v7';
var token = 'pk.eyJ1IjoicmVkdWNlciIsImEiOiJrS3k2czVJIn0.CjwU0V9fO4FAf3ukyV4eqQ';
var url = 'https://b.tiles.mapbox.com/v4/' + ids + '/{z}/{x}/{y}.vector.pbf?access_token=' + token;
const ids = 'mapbox.mapbox-streets-v7';
const token = 'pk.eyJ1IjoicmVkdWNlciIsImEiOiJrS3k2czVJIn0.CjwU0V9fO4FAf3ukyV4eqQ';
const url = `https://b.tiles.mapbox.com/v4/${ids}/{z}/{x}/{y}.vector.pbf?access_token=${ token}`;

var readTime = 0;
var writeTime = 0;
var size = 0;
var numTiles = 0;
let readTime = 0;
let writeTime = 0;
let size = 0;
let numTiles = 0;

runStats(url, processTile, showStats, {
width: 2880,
Expand All @@ -25,14 +24,14 @@ function processTile(body) {
size += body.length;
numTiles++;

var now = clock();
var tile = Tile.read(new Pbf(body));
let now = clock();
const tile = readTile(new Pbf(body));
readTime += clock(now);

now = clock();
var pbf = new Pbf();
Tile.write(tile, pbf);
var buf = pbf.finish();
const pbf = new Pbf();
writeTile(tile, pbf);
const buf = pbf.finish();
writeTime += clock(now);

console.assert(buf);
Expand All @@ -50,7 +49,7 @@ function speed(time, size) {

function clock(start) {
if (!start) return process.hrtime();
var t = process.hrtime(start);
const t = process.hrtime(start);
return t[0] * 1e3 + t[1] * 1e-6;
}

29 changes: 16 additions & 13 deletions bench/bench.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,37 +5,40 @@
<meta charset='utf-8'>
</head>
<body>
<script>var exports = {};</script>
<script src='../dist/pbf-dev.js'></script>
<script src='data.js'></script>
<script src='vector_tile.js'></script>
<script>
<script type="module">
import Pbf from '../index.js';
import {readTile, writeTile} from './vector_tile.js';

function read(data) {
return Tile.read(new Pbf(data));
return readTile(new Pbf(data));
}
function write(tile) {
var pbf = new Pbf();
Tile.write(tile, pbf);
writeTile(tile, pbf);
return pbf.finish();
}

var tile = read(data),
tileJSON = JSON.stringify(tile);
const data = await (await fetch('../test/fixtures/12665.vector.pbf')).arrayBuffer();

const tile = read(data);
const tileJSON = JSON.stringify(tile);

const N = 100;

console.time('decode');
for (var i = 0; i < 10; i++) read(data);
for (let i = 0; i < N; i++) read(data);
console.timeEnd('decode');

console.time('encode');
for (var i = 0; i < 10; i++) write(tile);
for (let i = 0; i < N; i++) write(tile);
console.timeEnd('encode');

console.time('JSON.parse');
for (var i = 0; i < 10; i++) JSON.parse(tileJSON);
for (let i = 0; i < N; i++) JSON.parse(tileJSON);
console.timeEnd('JSON.parse');

console.time('JSON.stringify');
for (var i = 0; i < 10; i++) JSON.stringify(tile);
for (let i = 0; i < N; i++) JSON.stringify(tile);
console.timeEnd('JSON.stringify');
</script>
</body>
Expand Down
32 changes: 15 additions & 17 deletions bench/bench.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,31 @@
'use strict';

var Benchmark = require('benchmark'),
fs = require('fs'),
path = require('path'),
protocolBuffers = require('protocol-buffers'),
protobufjs = require('protobufjs'),
vt = require('./vector_tile'),
Pbf = require('../');
import Benchmark from 'benchmark';
import fs from 'fs';
import {fileURLToPath} from 'node:url';
import protocolBuffers from 'protocol-buffers';
import protobufjs from 'protobufjs';

var pbfReadTile = vt.Tile.read,
pbfWriteTile = vt.Tile.write,
data = fs.readFileSync(path.resolve(__dirname, '../test/fixtures/12665.vector.pbf')),
import {readTile, writeTile} from '../test/fixtures/vector_tile.js';
import Pbf from '../index.js';

var data = fs.readFileSync(new URL('../test/fixtures/12665.vector.pbf', import.meta.url)),
suite = new Benchmark.Suite(),
ProtocolBuffersTile = protocolBuffers(fs.readFileSync(path.resolve(__dirname, 'vector_tile.proto'))).Tile,
ProtobufjsTile = protobufjs.loadSync(path.resolve(__dirname, 'vector_tile.proto'))
.lookup('vector_tile.Tile');
vtProtoUrl = new URL('../test/fixtures/vector_tile.proto', import.meta.url),
ProtocolBuffersTile = protocolBuffers(fs.readFileSync(vtProtoUrl)).Tile,
ProtobufjsTile = protobufjs.loadSync(fileURLToPath(vtProtoUrl)).lookup('vector_tile.Tile');

var pbfTile = pbfReadTile(new Pbf(data)),
var pbfTile = readTile(new Pbf(data)),
tileJSON = JSON.stringify(pbfTile),
protocolBuffersTile = ProtocolBuffersTile.decode(data),
protobufjsTile = ProtobufjsTile.decode(data);

suite
.add('decode vector tile with pbf', function() {
pbfReadTile(new Pbf(data));
readTile(new Pbf(data));
})
.add('encode vector tile with pbf', function() {
var pbf = new Pbf();
pbfWriteTile(pbfTile, pbf);
writeTile(pbfTile, pbf);
pbf.finish();
})
.add('decode vector tile with protocol-buffers', function() {
Expand Down
1 change: 0 additions & 1 deletion bench/data.js

This file was deleted.

Loading