diff --git a/fluent-dom/CHANGELOG.md b/fluent-dom/CHANGELOG.md index 258a9b7c9..7f7d249bf 100644 --- a/fluent-dom/CHANGELOG.md +++ b/fluent-dom/CHANGELOG.md @@ -7,6 +7,11 @@ Currently supported are: Firefox 52+, Chrome 55+, Edge 15+, Safari 10.1+, iOS Safari 10.3+ and node 8.9+. + - Add the `cached-iterable` runtime dependency. + + `CachedAsyncIterable` is now available from its own package rather than + from the `fluent` package. + ## fluent-dom 0.3.0 - Refactor the overlay sanitization methods into separate functions. (#189) diff --git a/fluent-dom/makefile b/fluent-dom/makefile index fd08cb613..57e11a422 100644 --- a/fluent-dom/makefile +++ b/fluent-dom/makefile @@ -1,5 +1,6 @@ PACKAGE := fluent-dom GLOBAL := FluentDOM +DEPS := cached-iterable:CachedIterable include ../common.mk @@ -11,6 +12,7 @@ $(PACKAGE).js: $(SOURCES) --banner "/* $(PACKAGE)@$(VERSION) */" \ --amd.id $(PACKAGE) \ --name $(GLOBAL) \ + --globals $(DEPS) \ --output.file $@ @echo -e " $(OK) $@ built" @@ -20,6 +22,7 @@ compat.js: $(SOURCES) --banner "/* $(PACKAGE)@$(VERSION) */" \ --amd.id $(PACKAGE) \ --name $(GLOBAL) \ + --globals $(DEPS) \ --output.file $@ @echo -e " $(OK) $@ built" diff --git a/fluent-dom/package.json b/fluent-dom/package.json index 97e5f8319..cf27972c3 100644 --- a/fluent-dom/package.json +++ b/fluent-dom/package.json @@ -40,5 +40,8 @@ }, "devDependencies": { "jsdom": "^11.6.2" + }, + "dependencies": { + "cached-iterable": "^0.2.1" } } diff --git a/fluent-dom/src/localization.js b/fluent-dom/src/localization.js index 0feb980da..3dd882eea 100644 --- a/fluent-dom/src/localization.js +++ b/fluent-dom/src/localization.js @@ -1,7 +1,7 @@ /* eslint no-console: ["error", { allow: ["warn", "error"] }] */ /* global console */ -import { CachedAsyncIterable } from "../../fluent/src/index"; +import { CachedAsyncIterable } from "cached-iterable"; /** * The `Localization` class is a central high-level API for vanilla @@ -20,8 +20,8 @@ export default class Localization { constructor(resourceIds = [], generateMessages) { this.resourceIds = resourceIds; this.generateMessages = generateMessages; - this.ctxs = - new CachedAsyncIterable(this.generateMessages(this.resourceIds)); + this.ctxs = CachedAsyncIterable.from( + this.generateMessages(this.resourceIds)); } addResourceIds(resourceIds) { @@ -154,8 +154,8 @@ export default class Localization { * that language negotiation or available resources changed. */ onChange() { - this.ctxs = - new CachedAsyncIterable(this.generateMessages(this.resourceIds)); + this.ctxs = CachedAsyncIterable.from( + this.generateMessages(this.resourceIds)); this.ctxs.touchNext(2); } } diff --git a/fluent-gecko/xpcom_config.js b/fluent-gecko/xpcom_config.js index 5fcddb404..336501b0c 100644 --- a/fluent-gecko/xpcom_config.js +++ b/fluent-gecko/xpcom_config.js @@ -1,3 +1,4 @@ +import nodeResolve from 'rollup-plugin-node-resolve'; import bundleConfig from '../bundle_config'; const version = require('../fluent-dom/package.json').version; @@ -24,5 +25,8 @@ export default Object.assign({}, bundleConfig, { * limitations under the License. */\n\n`, intro: `/* fluent-dom@${version} */`, - } + }, + plugins: [ + nodeResolve(), + ] }); diff --git a/fluent-react/CHANGELOG.md b/fluent-react/CHANGELOG.md index dff8882d0..223f1fe5a 100644 --- a/fluent-react/CHANGELOG.md +++ b/fluent-react/CHANGELOG.md @@ -7,6 +7,11 @@ Currently supported are: Firefox 52+, Chrome 55+, Edge 15+, Safari 10.1+, iOS Safari 10.3+ and node 8.9+. + - Add the `cached-iterable` runtime dependency. + + `CachedSyncIterable` is now available from its own package rather than + from the `fluent` package. + ## fluent-react 0.7.0 (May 18, 2018) - Protect void elements against translated text content. (#174) diff --git a/fluent-react/compat_config.js b/fluent-react/compat_config.js index 08c3f0749..42deb7e88 100644 --- a/fluent-react/compat_config.js +++ b/fluent-react/compat_config.js @@ -11,8 +11,16 @@ export default { plugins: [ ...babelConfig.plugins, ["babel-plugin-transform-rename-import", { - original: "fluent", - replacement: "fluent/compat", + replacements: [ + { + original: "fluent", + replacement: "fluent/compat", + }, + { + original: "cached-iterable", + replacement: "cached-iterable/compat", + }, + ] }], ] }), diff --git a/fluent-react/makefile b/fluent-react/makefile index c0f6470a3..3a4310077 100644 --- a/fluent-react/makefile +++ b/fluent-react/makefile @@ -1,6 +1,6 @@ PACKAGE := fluent-react GLOBAL := FluentReact -DEPS := fluent:Fluent,react:React,prop-types:PropTypes +DEPS := fluent:Fluent,cached-iterable:CachedIterable,react:React,prop-types:PropTypes include ../common.mk @@ -22,7 +22,7 @@ compat.js: $(SOURCES) --banner "/* $(PACKAGE)@$(VERSION) */" \ --amd.id $(PACKAGE) \ --name $(GLOBAL) \ - --globals fluent/compat:Fluent,$(DEPS) \ + --globals fluent/compat:Fluent,cached-iterable/compat:CachedIterable,$(DEPS) \ --output.file $@ @echo -e " $(OK) $@ built" diff --git a/fluent-react/package.json b/fluent-react/package.json index a7b13c3cc..56ad6ab7a 100644 --- a/fluent-react/package.json +++ b/fluent-react/package.json @@ -46,6 +46,7 @@ "node": ">=8.9.0" }, "dependencies": { + "cached-iterable": "^0.2.1", "fluent": "^0.4.0 || ^0.6.0", "prop-types": "^15.6.0" }, diff --git a/fluent-react/src/localization.js b/fluent-react/src/localization.js index 70089486d..63812cb24 100644 --- a/fluent-react/src/localization.js +++ b/fluent-react/src/localization.js @@ -1,4 +1,5 @@ -import { CachedIterable, mapContextSync } from "fluent"; +import { mapContextSync } from "fluent"; +import { CachedSyncIterable } from "cached-iterable"; /* * `ReactLocalization` handles translation formatting and fallback. @@ -17,7 +18,7 @@ import { CachedIterable, mapContextSync } from "fluent"; */ export default class ReactLocalization { constructor(messages) { - this.contexts = new CachedIterable(messages); + this.contexts = CachedSyncIterable.from(messages); this.subs = new Set(); } @@ -39,7 +40,7 @@ export default class ReactLocalization { * Set a new `messages` iterable and trigger the retranslation. */ setMessages(messages) { - this.contexts = new CachedIterable(messages); + this.contexts = CachedSyncIterable.from(messages); // Update all subscribed Localized components. this.subs.forEach(comp => comp.relocalize()); diff --git a/fluent/CHANGELOG.md b/fluent/CHANGELOG.md index 42f7cfcc6..bf10b658f 100644 --- a/fluent/CHANGELOG.md +++ b/fluent/CHANGELOG.md @@ -7,6 +7,10 @@ Currently supported are: Firefox 52+, Chrome 55+, Edge 15+, Safari 10.1+, iOS Safari 10.3+ and node 8.9+. + - Move CachedSyncIterable and CachedAsyncIterable to an external dependency. + + They are now available from the `cached-iterable` package. + ## fluent 0.6.4 (April 11, 2018) - Minor optimization to bidirectionality isolation diff --git a/fluent/package.json b/fluent/package.json index 740118bc8..f45dc8b9f 100644 --- a/fluent/package.json +++ b/fluent/package.json @@ -44,6 +44,7 @@ "node": ">=8.9.0" }, "devDependencies": { + "cached-iterable": "^0.2.1", "sinon": "^4.2.2" } } diff --git a/fluent/src/cached_iterable.js b/fluent/src/cached_iterable.js deleted file mode 100644 index 9500e683a..000000000 --- a/fluent/src/cached_iterable.js +++ /dev/null @@ -1,109 +0,0 @@ -/* - * CachedSyncIterable caches the elements yielded by an iterable. - * - * It can be used to iterate over an iterable many times without depleting the - * iterable. - */ -export class CachedSyncIterable { - /** - * Create an `CachedSyncIterable` instance. - * - * @param {Iterable} iterable - * @returns {CachedSyncIterable} - */ - constructor(iterable) { - if (Symbol.iterator in Object(iterable)) { - this.iterator = iterable[Symbol.iterator](); - } else { - throw new TypeError("Argument must implement the iteration protocol."); - } - - this.seen = []; - } - - [Symbol.iterator]() { - const { seen, iterator } = this; - let cur = 0; - - return { - next() { - if (seen.length <= cur) { - seen.push(iterator.next()); - } - return seen[cur++]; - } - }; - } - - /** - * This method allows user to consume the next element from the iterator - * into the cache. - * - * @param {number} count - number of elements to consume - */ - touchNext(count = 1) { - const { seen, iterator } = this; - let idx = 0; - while (idx++ < count) { - if (seen.length === 0 || seen[seen.length - 1].done === false) { - seen.push(iterator.next()); - } - } - } -} - -/* - * CachedAsyncIterable caches the elements yielded by an async iterable. - * - * It can be used to iterate over an iterable many times without depleting the - * iterable. - */ -export class CachedAsyncIterable { - /** - * Create an `CachedAsyncIterable` instance. - * - * @param {Iterable} iterable - * @returns {CachedAsyncIterable} - */ - constructor(iterable) { - if (Symbol.asyncIterator in Object(iterable)) { - this.iterator = iterable[Symbol.asyncIterator](); - } else if (Symbol.iterator in Object(iterable)) { - this.iterator = iterable[Symbol.iterator](); - } else { - throw new TypeError("Argument must implement the iteration protocol."); - } - - this.seen = []; - } - - [Symbol.asyncIterator]() { - const { seen, iterator } = this; - let cur = 0; - - return { - async next() { - if (seen.length <= cur) { - seen.push(await iterator.next()); - } - return seen[cur++]; - } - }; - } - - /** - * This method allows user to consume the next element from the iterator - * into the cache. - * - * @param {number} count - number of elements to consume - */ - async touchNext(count = 1) { - const { seen, iterator } = this; - let idx = 0; - while (idx++ < count) { - if (seen.length === 0 || seen[seen.length - 1].done === false) { - seen.push(await iterator.next()); - } - } - } -} diff --git a/fluent/src/index.js b/fluent/src/index.js index b0783fc7c..15cf18f0b 100644 --- a/fluent/src/index.js +++ b/fluent/src/index.js @@ -16,7 +16,6 @@ export { FluentDateTime as MessageDateTimeArgument, } from "./types"; -export { CachedSyncIterable, CachedAsyncIterable } from "./cached_iterable"; export { mapContextSync, mapContextAsync } from "./fallback"; export { ftl } from "./util"; diff --git a/fluent/test/cached_iterable_test.js b/fluent/test/cached_iterable_test.js deleted file mode 100644 index f095b3366..000000000 --- a/fluent/test/cached_iterable_test.js +++ /dev/null @@ -1,276 +0,0 @@ -import assert from 'assert'; - -import { CachedSyncIterable, CachedAsyncIterable } from '../src/cached_iterable'; - -/** - * Return a promise for an array with all the elements of the iterable. - * - * It uses for-await to support async iterables which can't be spread with - * ...iterable. See https://github.com/tc39/proposal-async-iteration/issues/103 - * - */ -async function toArray(iterable) { - const result = []; - for await (const elem of iterable) { - result.push(elem); - } - return result; -} - -suite('CachedSyncIterable', function() { - suite('constructor errors', function(){ - test('no argument', function() { - function run() { - new CachedSyncIterable(); - } - - assert.throws(run, TypeError); - assert.throws(run, /iteration protocol/); - }); - - test('null argument', function() { - function run() { - new CachedSyncIterable(null); - } - - assert.throws(run, TypeError); - assert.throws(run, /iteration protocol/); - }); - - test('bool argument', function() { - function run() { - new CachedSyncIterable(1); - } - - assert.throws(run, TypeError); - assert.throws(run, /iteration protocol/); - }); - - test('number argument', function() { - function run() { - new CachedSyncIterable(1); - } - - assert.throws(run, TypeError); - assert.throws(run, /iteration protocol/); - }); - }); - - suite('sync iteration', function(){ - let o1, o2; - - suiteSetup(function() { - o1 = Object(); - o2 = Object(); - }); - - test('eager iterable', function() { - const iterable = new CachedSyncIterable([o1, o2]); - assert.deepEqual([...iterable], [o1, o2]); - }); - - test('eager iterable works more than once', function() { - const iterable = new CachedSyncIterable([o1, o2]); - assert.deepEqual([...iterable], [o1, o2]); - assert.deepEqual([...iterable], [o1, o2]); - }); - - test('lazy iterable', function() { - function *generate() { - yield *[o1, o2]; - } - - const iterable = new CachedSyncIterable(generate()); - assert.deepEqual([...iterable], [o1, o2]); - }); - - test('lazy iterable works more than once', function() { - function *generate() { - let i = 2; - - while (--i) { - yield Object(); - } - } - - const iterable = new CachedSyncIterable(generate()); - const first = [...iterable]; - assert.deepEqual([...iterable], first); - }); - }); - - suite('async iteration', function(){ - let o1, o2; - - suiteSetup(function() { - o1 = Object(); - o2 = Object(); - }); - - test('lazy iterable', async function() { - async function *generate() { - yield *[o1, o2]; - } - - const iterable = new CachedAsyncIterable(generate()); - assert.deepEqual(await toArray(iterable), [o1, o2]); - }); - - test('lazy iterable works more than once', async function() { - async function *generate() { - let i = 2; - - while (--i) { - yield Object(); - } - } - - const iterable = new CachedAsyncIterable(generate()); - const first = await toArray(iterable); - assert.deepEqual(await toArray(iterable), first); - }); - }); - - suite('touchNext', function(){ - let o1, o2; - - suiteSetup(function() { - o1 = Object(); - o2 = Object(); - }); - - test('consumes an element into the cache', function() { - const iterable = new CachedSyncIterable([o1, o2]); - assert.equal(iterable.seen.length, 0); - iterable.touchNext(); - assert.equal(iterable.seen.length, 1); - }); - - test('allows to consume multiple elements into the cache', function() { - const iterable = new CachedSyncIterable([o1, o2]); - iterable.touchNext(); - iterable.touchNext(); - assert.equal(iterable.seen.length, 2); - }); - - test('allows to consume multiple elements at once', function() { - const iterable = new CachedSyncIterable([o1, o2]); - iterable.touchNext(2); - assert.equal(iterable.seen.length, 2); - }); - - test('stops at the last element', function() { - const iterable = new CachedSyncIterable([o1, o2]); - iterable.touchNext(); - iterable.touchNext(); - iterable.touchNext(); - assert.equal(iterable.seen.length, 3); - - iterable.touchNext(); - assert.equal(iterable.seen.length, 3); - }); - - test('works on an empty iterable', function() { - const iterable = new CachedSyncIterable([]); - iterable.touchNext(); - iterable.touchNext(); - iterable.touchNext(); - assert.equal(iterable.seen.length, 1); - }); - - test('iteration for such cache works', function() { - const iterable = new CachedSyncIterable([o1, o2]); - iterable.touchNext(); - iterable.touchNext(); - iterable.touchNext(); - assert.deepEqual([...iterable], [o1, o2]); - }); - }); - - suite('async touchNext', function(){ - let o1, o2, generateMessages; - - suiteSetup(function() { - o1 = Object(); - o2 = Object(); - - generateMessages = async function *generateMessages() { - yield *[o1, o2]; - } - }); - - test('consumes an element into the cache', async function() { - const iterable = new CachedAsyncIterable(generateMessages()); - assert.equal(iterable.seen.length, 0); - await iterable.touchNext(); - assert.equal(iterable.seen.length, 1); - }); - - test('allows to consume multiple elements into the cache', async function() { - const iterable = new CachedAsyncIterable(generateMessages()); - await iterable.touchNext(); - await iterable.touchNext(); - assert.equal(iterable.seen.length, 2); - }); - - test('allows to consume multiple elements at once', async function() { - const iterable = new CachedAsyncIterable(generateMessages()); - await iterable.touchNext(2); - assert.equal(iterable.seen.length, 2); - }); - - test('stops at the last element', async function() { - const iterable = new CachedAsyncIterable(generateMessages()); - await iterable.touchNext(); - await iterable.touchNext(); - await iterable.touchNext(); - assert.equal(iterable.seen.length, 3); - - await iterable.touchNext(); - assert.equal(iterable.seen.length, 3); - }); - - test('works on an empty iterable', async function() { - async function *generateEmptyMessages() { - yield *[]; - } - const iterable = new CachedAsyncIterable(generateEmptyMessages()); - await iterable.touchNext(); - await iterable.touchNext(); - await iterable.touchNext(); - assert.equal(iterable.seen.length, 1); - }); - - test('iteration for such cache works', async function() { - const iterable = new CachedAsyncIterable(generateMessages()); - await iterable.touchNext(); - await iterable.touchNext(); - await iterable.touchNext(); - - // It's a bit quirky compared to the sync counterpart, - // but there's no good way to fold async iterator into - // an array. - let values = []; - for await (let elem of iterable) { - values.push(elem); - } - assert.deepEqual(values, [o1, o2]); - }); - - test('async version handles sync iterator', async function() { - const iterable = new CachedAsyncIterable([o1, o2]); - await iterable.touchNext(); - await iterable.touchNext(); - await iterable.touchNext(); - - // It's a bit quirky compared to the sync counterpart, - // but there's no good way to fold async iterator into - // an array. - let values = []; - for await (let elem of iterable) { - values.push(elem); - } - assert.deepEqual(values, [o1, o2]); - }); - }); -}); diff --git a/fluent/test/fallback_async_test.js b/fluent/test/fallback_async_test.js index 943de9ca7..ec037dc68 100644 --- a/fluent/test/fallback_async_test.js +++ b/fluent/test/fallback_async_test.js @@ -1,6 +1,6 @@ import assert from 'assert'; -import { CachedAsyncIterable } from '../src/cached_iterable'; +import { CachedAsyncIterable } from 'cached-iterable'; import MessageContext from './message_context_stub'; import { mapContextAsync } from '../src/index'; diff --git a/fluent/test/fallback_sync_test.js b/fluent/test/fallback_sync_test.js index 404c2eacf..503526aab 100644 --- a/fluent/test/fallback_sync_test.js +++ b/fluent/test/fallback_sync_test.js @@ -1,6 +1,6 @@ import assert from 'assert'; -import { CachedSyncIterable } from '../src/cached_iterable'; +import { CachedSyncIterable } from 'cached-iterable'; import MessageContext from './message_context_stub'; import { mapContextSync } from '../src/index';