bundlebee 0.1.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js ADDED
@@ -0,0 +1,400 @@
1
+ const traverse = require('bare-module-traverse')
2
+ const Bundle = require('bare-bundle')
3
+ const Module = require('bare-module')
4
+ const { fileURLToPath } = require('url-file-url')
5
+ const fs = require('fs')
6
+ const b4a = require('b4a')
7
+ const ReadyResource = require('ready-resource')
8
+ const Bee = require('hyperbee2')
9
+ const c = require('compact-encoding')
10
+ const { getEncoding } = require('./schema')
11
+
12
+ const Entry = getEncoding('@bundlebee/entry')
13
+ const Manifest = getEncoding('@bundlebee/manifest')
14
+ const PeerDeps = getEncoding('@bundlebee/peer-deps')
15
+
16
+ const MANIFEST_KEY_VALUE = '#manifest'
17
+ const MANIFEST_KEY = b4a.from(MANIFEST_KEY_VALUE)
18
+ const PEERDEPS_KEY_VALUE = '#peer-deps'
19
+ const PEERDEPS_KEY = b4a.from(PEERDEPS_KEY_VALUE)
20
+
21
+ // TODO
22
+ // peer deps
23
+
24
+ module.exports = class Bundlebee extends ReadyResource {
25
+ constructor(store, opts = {}) {
26
+ super()
27
+ this._bee = new Bee(store, { autoUpdate: true, ...opts })
28
+
29
+ this.ready().catch(noop)
30
+ }
31
+
32
+ static async require(store, ...files) {
33
+ const opts =
34
+ files.length &&
35
+ typeof files[files.length - 1] === 'object' &&
36
+ !('bundle' in files[files.length - 1])
37
+ ? files.pop()
38
+ : undefined
39
+
40
+ const b = new Bundlebee(store, opts)
41
+ const manifest = await b.manifest()
42
+
43
+ // skip requires an existing manifest
44
+ const skipExistingABIs = opts && !!opts.skipExistingABIs && !!manifest
45
+ const peerDependencies = opts?.peerDependencies
46
+
47
+ // Early exit
48
+ const lastAbi =
49
+ files.length && typeof files[files.length - 1] === 'object'
50
+ ? files[files.length - 1].abi || 0
51
+ : 0
52
+ if (skipExistingABIs && lastAbi <= manifest.abi) return b
53
+
54
+ const bundles = files.reduce((all, f) => {
55
+ const data = typeof f === 'object' ? f : { bundle: f, abi: 0 }
56
+ if (skipExistingABIs && data.abi <= manifest.abi) return all
57
+
58
+ all.push({
59
+ ...data,
60
+ bundle: Bundlebee.bundleFrom(data.bundle)
61
+ })
62
+
63
+ return all
64
+ }, [])
65
+
66
+ for (const bu of bundles) {
67
+ await b._addBundle(bu, peerDependencies)
68
+ }
69
+
70
+ return b
71
+ }
72
+
73
+ static bundleFrom(f) {
74
+ if (f.endsWith('.js')) {
75
+ const bundle = require(f)
76
+ return bundle
77
+ }
78
+
79
+ return Bundle.from(fs.readFileSync(f))
80
+ }
81
+
82
+ get core() {
83
+ return this._bee.core
84
+ }
85
+
86
+ head() {
87
+ return this._bee.head()
88
+ }
89
+
90
+ get key() {
91
+ return this._bee.core.key
92
+ }
93
+
94
+ get discoveryKey() {
95
+ return this._bee.core.discoveryKey
96
+ }
97
+
98
+ _open() {
99
+ return this._bee.ready()
100
+ }
101
+
102
+ async *createEntryStream(checkout) {
103
+ const b = checkout ? await this.checkout(checkout) : this._bee
104
+
105
+ for await (const data of b.createReadStream()) {
106
+ const id = data.key.toString()
107
+ if (id === MANIFEST_KEY_VALUE || id === PEERDEPS_KEY_VALUE) continue
108
+ const entry = c.decode(Entry, data.value)
109
+ yield { ...entry, id }
110
+ }
111
+ }
112
+
113
+ async manifest(checkout) {
114
+ if (!this.opened) await this.ready()
115
+
116
+ const b = checkout ? await this.checkout(checkout) : this._bee
117
+
118
+ const entry = await b.get(MANIFEST_KEY)
119
+ if (!entry) return null
120
+
121
+ return c.decode(Manifest, entry.value)
122
+ }
123
+
124
+ async peerDependencies(checkout) {
125
+ if (!this.opened) await this.ready()
126
+
127
+ const b = checkout ? await this.checkout(checkout) : this._bee
128
+
129
+ const entry = await b.get(b4a.from('#peer-deps'))
130
+ if (!entry) return null
131
+
132
+ const { packages } = c.decode(PeerDeps, entry.value)
133
+
134
+ return new Set(packages)
135
+ }
136
+
137
+ async get(key, checkout) {
138
+ if (!this.opened) await this.ready()
139
+
140
+ const b = checkout ? await this.checkout(checkout) : this._bee
141
+
142
+ const entry = await b.get(b4a.from(key))
143
+ if (!entry) return null
144
+
145
+ return c.decode(Entry, entry.value)
146
+ }
147
+
148
+ async findABI(abi) {
149
+ for await (const d of this._bee.createChangesStream()) {
150
+ let record = null
151
+ for (const b of d.batch) {
152
+ if (!b.keys) continue
153
+ const r = b.keys.find((k) => k.key.toString() === MANIFEST_KEY_VALUE)
154
+ if (!r) continue
155
+ record = r
156
+ }
157
+ if (!record) continue
158
+
159
+ const manifest = c.decode(Manifest, record.value)
160
+ if (manifest.abi !== abi) continue
161
+
162
+ return d.head
163
+ }
164
+ }
165
+
166
+ async checkout(checkout) {
167
+ if (!this.opened) await this.ready()
168
+
169
+ return this._bee.checkout(checkout)
170
+ }
171
+
172
+ async load(root, entry, checkout, { cache = require.cache, skipModules = true } = {}) {
173
+ if (!this.opened) await this.ready()
174
+ if (!(await this.get(entry, checkout))) throw new Error(`${entry} not found`)
175
+
176
+ const b = await this.checkout(checkout)
177
+ const loadedData = new Map()
178
+
179
+ const protocol = new Module.Protocol({
180
+ exists(url) {
181
+ return true
182
+ },
183
+ read(url) {
184
+ const p = url.pathname
185
+ return loadedData.get(p)
186
+ }
187
+ })
188
+
189
+ const bundleCache = Object.create(null)
190
+ const allResolutions = Object.create(null)
191
+
192
+ const peerDeps = await this.peerDependencies(checkout)
193
+
194
+ for await (const data of b.createReadStream()) {
195
+ const id = data.key.toString()
196
+ if (id === MANIFEST_KEY_VALUE || id === PEERDEPS_KEY_VALUE) continue
197
+
198
+ const { resolutions, source } = c.decode(Entry, data.value)
199
+
200
+ loadedData.set(id, source)
201
+
202
+ const m = {}
203
+ for (const [k, v] of Object.entries(resolutions)) {
204
+ const skip = peerDeps && peerDeps.has(k)
205
+ const nm =
206
+ (v.startsWith('/node_modules') && skipModules) || skip ? findModule(cache, v, root) : null
207
+ if (nm) {
208
+ m[k] = 'bundle://host' + v
209
+ bundleCache[m[k]] = nm
210
+ } else {
211
+ m[k] = 'bundle://layer' + v
212
+ }
213
+ }
214
+ allResolutions['bundle://layer' + id] = m
215
+ }
216
+
217
+ return Module.load(new URL(entry, 'bundle://layer/'), {
218
+ protocol,
219
+ resolutions: allResolutions,
220
+ cache: bundleCache
221
+ })
222
+ }
223
+
224
+ async add(root, entry, { skipModules = true, peerDependencies } = {}) {
225
+ if (!this.opened) await this.ready()
226
+ if (!root.pathname.endsWith('/')) root = new URL('./', root)
227
+ if (peerDependencies) peerDependencies = new Set(peerDependencies)
228
+
229
+ const nodeModules = new URL('./node_modules', root)
230
+ const bundle = new Bundle()
231
+
232
+ const resolutions = {}
233
+
234
+ for await (const dependency of traverse(
235
+ new URL(entry, root),
236
+ { resolve: traverse.resolve.bare },
237
+ readModule,
238
+ listPrefix
239
+ )) {
240
+ if (dependency.url.href.startsWith(nodeModules.href)) {
241
+ if (skipModules) continue
242
+ if (peerDependencies) {
243
+ const moduleName = dependency.url.pathname
244
+ .replace(root.pathname + 'node_modules/', '')
245
+ .split('/')[0]
246
+ if (peerDependencies.has(moduleName)) continue
247
+ }
248
+ }
249
+
250
+ const p = dependency.url.pathname.replace(root.pathname, '/')
251
+ const imps = {}
252
+ for (const [k, v] of Object.entries(dependency.imports)) {
253
+ imps[k] = new URL(v).pathname.replace(root.pathname, '/')
254
+ }
255
+
256
+ const existing = await this.get(p)
257
+ if (
258
+ existing &&
259
+ b4a.equals(existing.source, dependency.source) &&
260
+ sameImports(existing.resolutions, imps)
261
+ ) {
262
+ continue
263
+ }
264
+
265
+ bundle.write(p, dependency.source)
266
+ resolutions[p] = imps
267
+ }
268
+
269
+ bundle.resolutions = resolutions
270
+
271
+ await this._addBundle({ bundle }, peerDependencies)
272
+
273
+ return bundle
274
+ }
275
+
276
+ async _addBundle(data, peerDependencies) {
277
+ if (!this.opened) await this.ready()
278
+
279
+ let nextAbi = data.abi
280
+ const previousManifest = await this.manifest()
281
+ if (nextAbi && previousManifest && nextAbi <= previousManifest.abi) {
282
+ throw new Error(`ABI ${nextAbi} <= to current ABI ${previousManifest.abi}`)
283
+ } else if (!nextAbi) {
284
+ nextAbi = previousManifest ? previousManifest.abi + 1 : 1
285
+ }
286
+
287
+ const w = this._bee.write()
288
+ for (const f in data.bundle.files) {
289
+ // TODO: make a schema for resolutions value
290
+ // source + resolutions map
291
+
292
+ w.tryPut(
293
+ b4a.from(f),
294
+ c.encode(Entry, {
295
+ source: data.bundle.files[f].read(),
296
+ resolutions: data.bundle.resolutions[f]
297
+ })
298
+ )
299
+ }
300
+
301
+ if (peerDependencies) {
302
+ w.tryPut(
303
+ b4a.from(PEERDEPS_KEY),
304
+ c.encode(PeerDeps, {
305
+ packages: [...peerDependencies]
306
+ })
307
+ )
308
+ }
309
+
310
+ w.tryPut(
311
+ b4a.from(MANIFEST_KEY),
312
+ c.encode(Manifest, {
313
+ abi: nextAbi
314
+ })
315
+ )
316
+
317
+ await w.flush()
318
+ }
319
+ }
320
+
321
+ function noop() {}
322
+
323
+ function sameImports(a, b) {
324
+ const x = Object.keys(a)
325
+ const y = Object.keys(b)
326
+
327
+ if (x.length !== y.length) return false
328
+
329
+ for (let i = 0; i < x.length; i++) {
330
+ if (a[x[i]] !== b[x[i]]) return false
331
+ }
332
+
333
+ return true
334
+ }
335
+
336
+ async function readModule(url) {
337
+ return new Promise((resolve) => {
338
+ fs.readFile(fileURLToPath(url), (err, data) => {
339
+ resolve(err ? null : data)
340
+ })
341
+ })
342
+ }
343
+
344
+ async function openDir(url) {
345
+ return new Promise((resolve, reject) => {
346
+ fs.opendir(fileURLToPath(url), (err, dir) => {
347
+ err ? reject(err) : resolve(dir)
348
+ })
349
+ })
350
+ }
351
+
352
+ async function isFile(url) {
353
+ return new Promise((resolve) => {
354
+ fs.stat(fileURLToPath(url), (err, stat) => {
355
+ resolve(err === null && stat.isFile())
356
+ })
357
+ })
358
+ }
359
+
360
+ async function isDir(url) {
361
+ return new Promise((resolve) => {
362
+ fs.stat(fileURLToPath(url), (err, stat) => {
363
+ resolve(err === null && stat.isDirectory())
364
+ })
365
+ })
366
+ }
367
+
368
+ async function* listPrefix(url) {
369
+ if (await isFile(url)) return yield url
370
+
371
+ if (url.pathname[url.pathname.length - 1] !== '/') {
372
+ url.pathname += '/'
373
+ }
374
+
375
+ if (await isDir(url)) {
376
+ for await (const entry of await openDir(url)) {
377
+ if (entry.isDirectory()) {
378
+ yield* listPrefix(new URL(entry.name, url))
379
+ } else {
380
+ yield new URL(entry.name, url)
381
+ }
382
+ }
383
+ }
384
+ }
385
+
386
+ function findModule(cache, v, root) {
387
+ let s = '.'
388
+ let prev = null
389
+
390
+ while (true) {
391
+ const cand = new URL(s + v, root).href
392
+ s += '/..'
393
+ if (prev === cand) break
394
+ prev = cand
395
+ const nm = cache[cand]
396
+ if (nm) return nm
397
+ }
398
+
399
+ throw new Error(`failed to find module: ${v}`)
400
+ }
package/package.json CHANGED
@@ -1,39 +1,62 @@
1
1
  {
2
2
  "name": "bundlebee",
3
- "version": "0.1.0",
4
- "description": "Create bundles with a simple interface independant of the bundler",
5
- "main": "dist/bundlebee.js",
6
- "jsnext:main": "src/index.js",
3
+ "version": "1.0.0",
4
+ "description": "bundlebee",
5
+ "main": "index.js",
6
+ "imports": {
7
+ "fs": {
8
+ "bare": "bare-fs",
9
+ "default": "fs"
10
+ }
11
+ },
12
+ "exports": {
13
+ "./package": "./package.json",
14
+ ".": {
15
+ "types": "./index.d.ts",
16
+ "default": "./index.js"
17
+ }
18
+ },
19
+ "files": [
20
+ "package.json",
21
+ "index.js",
22
+ "index.d.ts",
23
+ "schema"
24
+ ],
25
+ "devDependencies": {
26
+ "brittle": "^3.19.0",
27
+ "corestore": "^7.8.0",
28
+ "hyperdht": "^6.29.0",
29
+ "hyperswarm": "^4.16.0",
30
+ "lunte": "^1.2.0",
31
+ "prettier": "^3.6.2",
32
+ "prettier-config-holepunch": "^2.0.0",
33
+ "test-tmp": "^1.4.0"
34
+ },
7
35
  "scripts": {
8
- "build": "rollup-babel-lib-bundler src/index.js",
9
- "test": "echo \"Error: no test specified\" && exit 1"
36
+ "format": "prettier . --write",
37
+ "test": "prettier . --check && lunte && brittle-bare test/index.js",
38
+ "lint": "lunte"
10
39
  },
11
40
  "repository": {
12
41
  "type": "git",
13
- "url": "git+https://github.com/frostney/bundlebee.git"
42
+ "url": "git+https://github.com/holepunchto/bundlebee.git"
14
43
  },
15
- "keywords": [
16
- "bundle",
17
- "bundler",
18
- "cmake",
19
- "make",
20
- "config"
21
- ],
22
- "files": [
23
- "dist",
24
- "src"
25
- ],
26
- "author": "Johannes Stein",
27
- "license": "MIT",
44
+ "author": "Holepunch Inc",
45
+ "license": "Apache-2.0",
28
46
  "bugs": {
29
- "url": "https://github.com/frostney/bundlebee/issues"
47
+ "url": "https://github.com/holepunchto/bundlebee/issues"
30
48
  },
31
- "homepage": "https://github.com/frostney/bundlebee#readme",
49
+ "homepage": "https://github.com/holepunchto/bundlebee",
32
50
  "dependencies": {
33
- "glob-to-regexp": "^0.1.0"
34
- },
35
- "devDependencies": {
36
- "babel-preset-es2015-loose-rollup": "^7.0.0",
37
- "rollup-babel-lib-bundler": "^2.1.1"
51
+ "b4a": "^1.7.4",
52
+ "bare-bundle": "^1.10.0",
53
+ "bare-fs": "^4.5.4",
54
+ "bare-module": "^6.1.3",
55
+ "bare-module-traverse": "^2.0.1",
56
+ "compact-encoding": "^2.19.0",
57
+ "hyperbee2": "^2.7.1",
58
+ "hyperschema": "^1.20.1",
59
+ "ready-resource": "^1.2.0",
60
+ "url-file-url": "^1.0.5"
38
61
  }
39
62
  }
@@ -0,0 +1,140 @@
1
+ // This file is autogenerated by the hyperschema compiler
2
+ // Schema Version: 1
3
+ /* eslint-disable camelcase */
4
+ /* eslint-disable quotes */
5
+ /* eslint-disable space-before-function-paren */
6
+
7
+ const { c } = require('hyperschema/runtime')
8
+
9
+ const VERSION = 1
10
+
11
+ // eslint-disable-next-line no-unused-vars
12
+ let version = VERSION
13
+
14
+ // @bundlebee/resolutions
15
+ const encoding0 = c.record(c.string, c.string)
16
+
17
+ // @bundlebee/entry
18
+ const encoding1 = {
19
+ preencode(state, m) {
20
+ c.buffer.preencode(state, m.source)
21
+ encoding0.preencode(state, m.resolutions)
22
+ },
23
+ encode(state, m) {
24
+ c.buffer.encode(state, m.source)
25
+ encoding0.encode(state, m.resolutions)
26
+ },
27
+ decode(state) {
28
+ const r0 = c.buffer.decode(state)
29
+ const r1 = encoding0.decode(state)
30
+
31
+ return {
32
+ source: r0,
33
+ resolutions: r1
34
+ }
35
+ }
36
+ }
37
+
38
+ // @bundlebee/manifest
39
+ const encoding2 = {
40
+ preencode(state, m) {
41
+ c.uint.preencode(state, m.abi)
42
+ },
43
+ encode(state, m) {
44
+ c.uint.encode(state, m.abi)
45
+ },
46
+ decode(state) {
47
+ const r0 = c.uint.decode(state)
48
+
49
+ return {
50
+ abi: r0
51
+ }
52
+ }
53
+ }
54
+
55
+ // @bundlebee/peer-deps.packages
56
+ const encoding3_0 = c.array(c.string)
57
+
58
+ // @bundlebee/peer-deps
59
+ const encoding3 = {
60
+ preencode(state, m) {
61
+ encoding3_0.preencode(state, m.packages)
62
+ },
63
+ encode(state, m) {
64
+ encoding3_0.encode(state, m.packages)
65
+ },
66
+ decode(state) {
67
+ const r0 = encoding3_0.decode(state)
68
+
69
+ return {
70
+ packages: r0
71
+ }
72
+ }
73
+ }
74
+
75
+ function setVersion(v) {
76
+ version = v
77
+ }
78
+
79
+ function encode(name, value, v = VERSION) {
80
+ version = v
81
+ return c.encode(getEncoding(name), value)
82
+ }
83
+
84
+ function decode(name, buffer, v = VERSION) {
85
+ version = v
86
+ return c.decode(getEncoding(name), buffer)
87
+ }
88
+
89
+ function getEnum(name) {
90
+ switch (name) {
91
+ default:
92
+ throw new Error('Enum not found ' + name)
93
+ }
94
+ }
95
+
96
+ function getEncoding(name) {
97
+ switch (name) {
98
+ case '@bundlebee/resolutions':
99
+ return encoding0
100
+ case '@bundlebee/entry':
101
+ return encoding1
102
+ case '@bundlebee/manifest':
103
+ return encoding2
104
+ case '@bundlebee/peer-deps':
105
+ return encoding3
106
+ default:
107
+ throw new Error('Encoder not found ' + name)
108
+ }
109
+ }
110
+
111
+ function getStruct(name, v = VERSION) {
112
+ const enc = getEncoding(name)
113
+ return {
114
+ preencode(state, m) {
115
+ version = v
116
+ enc.preencode(state, m)
117
+ },
118
+ encode(state, m) {
119
+ version = v
120
+ enc.encode(state, m)
121
+ },
122
+ decode(state) {
123
+ version = v
124
+ return enc.decode(state)
125
+ }
126
+ }
127
+ }
128
+
129
+ const resolveStruct = getStruct // compat
130
+
131
+ module.exports = {
132
+ resolveStruct,
133
+ getStruct,
134
+ getEnum,
135
+ getEncoding,
136
+ encode,
137
+ decode,
138
+ setVersion,
139
+ version
140
+ }
@@ -0,0 +1,61 @@
1
+ {
2
+ "version": 1,
3
+ "schema": [
4
+ {
5
+ "name": "resolutions",
6
+ "namespace": "bundlebee",
7
+ "record": true,
8
+ "key": "string",
9
+ "value": "string"
10
+ },
11
+ {
12
+ "name": "entry",
13
+ "namespace": "bundlebee",
14
+ "compact": false,
15
+ "flagsPosition": -1,
16
+ "fields": [
17
+ {
18
+ "name": "source",
19
+ "required": true,
20
+ "type": "buffer",
21
+ "version": 1
22
+ },
23
+ {
24
+ "name": "resolutions",
25
+ "required": true,
26
+ "type": "@bundlebee/resolutions",
27
+ "version": 1
28
+ }
29
+ ]
30
+ },
31
+ {
32
+ "name": "manifest",
33
+ "namespace": "bundlebee",
34
+ "compact": false,
35
+ "flagsPosition": -1,
36
+ "fields": [
37
+ {
38
+ "name": "abi",
39
+ "required": true,
40
+ "type": "uint",
41
+ "version": 1
42
+ }
43
+ ]
44
+ },
45
+ {
46
+ "name": "peer-deps",
47
+ "namespace": "bundlebee",
48
+ "compact": false,
49
+ "flagsPosition": -1,
50
+ "fields": [
51
+ {
52
+ "name": "packages",
53
+ "required": true,
54
+ "array": true,
55
+ "type": "string",
56
+ "version": 1
57
+ }
58
+ ]
59
+ }
60
+ ]
61
+ }