Skip to content

Commit 341be35

Browse files
authored
Add browser support (#48)
* feat(test): add browser test system * test(base): add suite on browser tests * refactor: rename browser tests * test(cache): add suite on browser tests * test(clear): add suite on browser tests * test(stale): add suite on browser tests * test(storage-base): add suite on browser tests * test(storage-memory): add suite on browser tests * test(ttl): add suite on browser tests * chore(storage): throw when runs in browser and redis * chore(memory): prevent undefined unref method * feat: add firefox support * feat: add safari support * refactor(abstract-logging): remove dependency and create factory * feat: add rollup bundler support * feat: add webpack bundler support * feat: add browserify bundler support * fix(bench): change createStorage path * chore(package): rename test browser script * feat(ci): add browser ci * chore(readme): add browser documentation * chore: reuse sleep helper method * perf(ci): cache playwright deps * perf(bundlers): dont include redis on browser bundles * feat(bundler): add vite support * chore(test): skip timer tests * chore(test): remove skipped tests * chore(readme): update browser specs * chore(util): comment abstractLogging method * chore(storage): remove istanbul and add test * chore(test-browser): remove duplicated tests * test(storage): add more specific tests * refactor: remove prepare command * ci(browser): remove prepare command
1 parent f08b148 commit 341be35

24 files changed

+756
-12
lines changed

.github/workflows/browsers-ci.yml

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
name: browsers ci
2+
3+
on:
4+
push:
5+
paths-ignore:
6+
- 'docs/**'
7+
- '*.md'
8+
pull_request:
9+
paths-ignore:
10+
- 'docs/**'
11+
- '*.md'
12+
13+
jobs:
14+
build:
15+
runs-on: ${{ matrix.os }}
16+
17+
strategy:
18+
fail-fast: false
19+
matrix:
20+
os: ['ubuntu-latest', 'windows-latest', 'macos-latest']
21+
browser: ['chrome', 'firefox', 'safari', 'edge']
22+
bundler: ['browserify', 'esbuild', 'rollup', 'vite', 'webpack']
23+
exclude:
24+
- os: ubuntu-latest
25+
browser: safari
26+
- os: windows-latest
27+
browser: safari
28+
steps:
29+
- uses: actions/checkout@v3
30+
31+
- name: Use Node.js
32+
uses: actions/setup-node@v3
33+
with:
34+
node-version: 18
35+
36+
- name: Restore cached playwright dependency
37+
uses: actions/cache@v3
38+
id: playwright-cache
39+
with:
40+
path: | # playwright installs browsers into .local-browsers
41+
~/.cache/ms-playwright
42+
~/Library/Caches/ms-playwright
43+
%LOCALAPPDATA%\ms-playwright
44+
key: ${{ matrix.os }}-playwright-${{ hashFiles('package.json') }}
45+
46+
- name: Restore cached dependencies
47+
uses: actions/cache@v3
48+
with:
49+
path: node_modules
50+
key: node-modules-${{ matrix.os }}-${{ hashFiles('package.json') }}
51+
52+
- name: Install dependencies
53+
run: npm install
54+
55+
- name: Install browser
56+
run: ./node_modules/.bin/playwright install ${{ fromJSON('{"chrome":"chromium","edge":"msedge","firefox":"firefox","safari":"webkit"}')[matrix.browser] }}
57+
58+
- name: Run Tests on Browsers
59+
run: npm run test:browser ${{ matrix.browser }} ${{ matrix.bundler }}

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,6 @@ dist
108108
.vscode
109109
docker-compose.yml
110110
dump.rdb
111+
112+
# Temporary files used for browser testing
113+
tmp/

README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,40 @@ const p1 = cache.fetchSomething(42) // <--- TypeScript doesn't argue anymore her
311311

312312
---
313313

314+
## Browser
315+
316+
All the major browser are supported; only `memory` storage type is supported, `redis` storage can't be used in a browser env.
317+
318+
This is a very simple example of how to use this module in a browser environment:
319+
320+
```html
321+
<script src="https://unpkg.com/async-cache-dedupe"></script>
322+
323+
<script>
324+
const cache = asyncCacheDedupe.createCache({
325+
ttl: 5, // seconds
326+
storage: { type: 'memory' },
327+
})
328+
329+
cache.define('fetchSomething', async (k) => {
330+
console.log('query', k)
331+
return { k }
332+
})
333+
334+
const p1 = cache.fetchSomething(42)
335+
const p2 = cache.fetchSomething(42)
336+
const p3 = cache.fetchSomething(42)
337+
338+
Promise.all([p1, p2, p3]).then((values) => {
339+
console.log(values)
340+
})
341+
</script>
342+
```
343+
344+
You can also use the module with a bundler. The supported bundlers are `webpack`, `rollup`, `esbuild` and `browserify`.
345+
346+
---
347+
314348
## Maintainers
315349

316350
* [__Matteo Collina__](https://github.com/mcollina), <https://twitter.com/matteocollina>, <https://www.npmjs.com/~matteo.collina>

bench/storage.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
const { hrtime } = require('process')
44
const path = require('path')
55
const Redis = require('ioredis')
6-
const createStorage = require(path.resolve(__dirname, '../storage'))
6+
const createStorage = require(path.resolve(__dirname, '../src/storage/index.js'))
77

88
// NOTE: this is a very basic benchmarks for tweaking
99
// performance is effected by keys and references size

package.json

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"types": "index.d.ts",
77
"scripts": {
88
"test": "standard | snazzy && tap test/*test.js && tsd",
9+
"test:browser": "node test/browser/helpers/runner-browser.mjs",
910
"lint:fix": "standard --fix",
1011
"redis": "docker run --rm -p 6379:6379 redis redis-server"
1112
},
@@ -30,15 +31,34 @@
3031
"license": "MIT",
3132
"devDependencies": {
3233
"@fastify/pre-commit": "^2.0.2",
34+
"@rollup/plugin-commonjs": "^24.0.1",
35+
"@rollup/plugin-inject": "^5.0.3",
36+
"@rollup/plugin-node-resolve": "^15.0.1",
37+
"browserify": "^17.0.0",
38+
"buffer": "^6.0.3",
39+
"esbuild": "^0.17.4",
40+
"esbuild-plugin-alias": "^0.2.1",
41+
"events": "^3.3.0",
3342
"ioredis": "^5.2.3",
43+
"path-browserify": "^1.0.1",
44+
"playwright": "^1.29.2",
45+
"process": "^0.11.10",
3446
"proxyquire": "^2.1.3",
47+
"rollup": "^3.11.0",
48+
"rollup-plugin-polyfill-node": "^0.12.0",
3549
"snazzy": "^9.0.0",
3650
"standard": "^17.0.0",
51+
"stream-browserify": "^3.0.0",
3752
"tap": "^16.3.0",
38-
"tsd": "^0.25.0"
53+
"tap-mocha-reporter": "^5.0.3",
54+
"tap-parser": "^12.0.1",
55+
"tape": "^5.6.3",
56+
"tsd": "^0.25.0",
57+
"vite": "^4.0.4",
58+
"webpack": "^5.75.0",
59+
"webpack-cli": "^5.0.1"
3960
},
4061
"dependencies": {
41-
"abstract-logging": "^2.0.1",
4262
"mnemonist": "^0.39.2",
4363
"safe-stable-stringify": "^2.3.1"
4464
},

src/storage/index.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
'use strict'
22

3-
const StorageRedis = require('./redis')
3+
const { isServerSide } = require('../util')
4+
5+
let StorageRedis
6+
if (isServerSide) {
7+
StorageRedis = require('./redis')
8+
}
49
const StorageMemory = require('./memory')
510

611
/**
@@ -27,6 +32,10 @@ const StorageOptionsType = {
2732
* @returns {StorageMemory|StorageRedis}
2833
*/
2934
function createStorage (type, options) {
35+
if (!isServerSide && type === StorageOptionsType.redis) {
36+
throw new Error('Redis storage is not supported in the browser')
37+
}
38+
3039
if (type === StorageOptionsType.redis) {
3140
return new StorageRedis(options)
3241
}

src/storage/memory.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use strict'
22

33
const LRUCache = require('mnemonist/lru-cache')
4-
const nullLogger = require('abstract-logging')
4+
const { abstractLogging } = require('../util')
55
const StorageInterface = require('./interface')
66
const { findMatchingIndexes, findNotMatching, bsearchIndex, wildcardMatch } = require('../util')
77

@@ -26,7 +26,7 @@ class StorageMemory extends StorageInterface {
2626

2727
super(options)
2828
this.size = options.size || DEFAULT_CACHE_SIZE
29-
this.log = options.log || nullLogger
29+
this.log = options.log || abstractLogging()
3030
this.invalidation = options.invalidation || false
3131

3232
this.init()
@@ -401,7 +401,9 @@ function now () {
401401
return _timer
402402
}
403403
_timer = Math.floor(Date.now() / 1000)
404-
setTimeout(_clearTimer, 1000).unref()
404+
const timeout = setTimeout(_clearTimer, 1000)
405+
// istanbul ignore next
406+
if (typeof timeout.unref === 'function') timeout.unref()
405407
return _timer
406408
}
407409

src/storage/redis.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
'use strict'
22

33
const stringify = require('safe-stable-stringify')
4-
const nullLogger = require('abstract-logging')
54
const StorageInterface = require('./interface')
6-
const { findNotMatching, randomSubset } = require('../util')
5+
const { findNotMatching, randomSubset, abstractLogging } = require('../util')
76

87
const GC_DEFAULT_CHUNK = 64
98
const GC_DEFAULT_LAZY_CHUNK = 64
@@ -33,7 +32,7 @@ class StorageRedis extends StorageInterface {
3332
throw new Error('invalidation.referencesTTL must be a positive integer greater than 1')
3433
}
3534

36-
this.log = options.log || nullLogger
35+
this.log = options.log || abstractLogging()
3736
this.store = options.client
3837
this.invalidation = !!options.invalidation
3938
this.referencesTTL = (options.invalidation && options.invalidation.referencesTTL) || REFERENCES_DEFAULT_TTL

src/util.js

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,11 +127,28 @@ function wildcardMatch (value, content) {
127127
return i >= value.length - 1
128128
}
129129

130+
// `abstract-logging` dependency has been removed because there is a bug on Rollup
131+
// https://github.com/jsumners/abstract-logging/issues/6
132+
function abstractLogging () {
133+
const noop = () => {}
134+
return {
135+
fatal: noop,
136+
error: noop,
137+
warn: noop,
138+
info: noop,
139+
debug: noop,
140+
trace: noop
141+
}
142+
}
143+
144+
const isServerSide = typeof window === 'undefined'
145+
130146
module.exports = {
131147
findNotMatching,
132148
findMatchingIndexes,
133149
bsearchIndex,
134150
wildcardMatch,
135-
136-
randomSubset
151+
randomSubset,
152+
abstractLogging,
153+
isServerSide
137154
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import * as processModule from 'process'
2+
3+
export const process = processModule
4+
5+
export function setImmediate (fn, ...args) {
6+
setTimeout(() => fn(...args), 0)
7+
}

0 commit comments

Comments
 (0)