Repository: kaliber5/ember-responsive-image
Pull request #113 (2021-01-22 22:36:11)

Refactor the ember-cli hooks integration, remove setupResponsiveImage test-helper
Now runs as part of `treeForPublic` instead of `postprocessTree` (which is not supported by Embroider). Also changed the way the meta data is accessed, removing the need for initializers, and thus also our test helper.

This one took me hours here: https://github.com/ember-fastboot/ember-cli-fastboot/issues/807 😔

/cc @andreasschacht @lolmaus 


Patch:
diff --git a/-fastboot/services/responsive-image.js b/-fastboot/services/responsive-image.js
new file mode 100644
index 00000000..44cd32c2
--- /dev/null
+++ b/-fastboot/services/responsive-image.js
@@ -0,0 +1,10 @@
+import BaseResponsiveImageService from 'ember-responsive-image/services/responsive-image';
+
+/**
+ * Extend Service in FastBoot
+ */
+export default class FastBootResponsiveImageService extends BaseResponsiveImageService {
+  get meta() {
+    return '__ember_responsive_image_meta__';
+  }
+}
diff --git a/.eslintrc.js b/.eslintrc.js
index 5e0f6e00..25e21bab 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -39,7 +39,7 @@ module.exports = {
         'blueprints/*/index.js',
         'config/**/*.js',
         'tests/dummy/config/**/*.js',
-        'broccoli-image-writer.js',
+        'lib/*.js',
       ],
       excludedFiles: [
         'addon/**',
diff --git a/README.md b/README.md
index 7990adbf..1cadff35 100644
--- a/README.md
+++ b/README.md
@@ -242,21 +242,6 @@ For an example see [ember-lazy-responsive-image](https://github.com/kaliber5/emb
 
 For lazy-loading and LQIP support, see [ember-lazy-responsive-image](https://github.com/kaliber5/ember-lazy-responsive-image).
 
-## Tests
-
-Sometimes you can get in trouble in integration tests and get an assertion `'There is no data for image ...'` if your subject relates to this addon. Because the `ResponsiveImageService` gets necessary data injected in an initializer, and initializers won't be called in the integration tests. To fix this, you can provided `setupResponsiveImage()` test helper in tests:
-
-```js
-import { setupRenderingTest} from 'ember-qunit';
-import { setupResponsiveImage } from 'ember-responsive-image/test-support';
-
-module('Integration: My Image Component', function(hooks) {
-  setupResponsiveImage(hooks);
-
-  test(...);
-});
-```
-
 Contributing
 ------------------------------------------------------------------------------
 
diff --git a/addon/initializers/responsive-meta.js b/addon/initializers/responsive-meta.js
deleted file mode 100644
index f01a967b..00000000
--- a/addon/initializers/responsive-meta.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import ResponsiveImage from '../services/responsive-image';
-
-export function initialize(/* appInstance */) {
-  if (typeof FastBoot === 'undefined') {
-    let meta = JSON.parse(
-      document.querySelector('#ember_responsive_image_meta').textContent
-    );
-    ResponsiveImage.reopen({
-      meta,
-    });
-  }
-}
-
-export default {
-  name: 'responsive-meta',
-  initialize,
-};
diff --git a/addon/services/responsive-image.js b/addon/services/responsive-image.js
index ca01f56b..99a029e1 100644
--- a/addon/services/responsive-image.js
+++ b/addon/services/responsive-image.js
@@ -46,7 +46,7 @@ export default class ResponsiveImageService extends Service {
    */
   getImages(imageName) {
     assert(
-      `There is no data for image ${imageName}`,
+      `There is no data for image ${imageName}: ${this.meta}`,
       Object.prototype.hasOwnProperty.call(this.meta, imageName)
     );
     assert(
@@ -137,4 +137,18 @@ export default class ResponsiveImageService extends Service {
    * @type {object}
    * @private
    */
+  get meta() {
+    if (this._meta) {
+      return this._meta;
+    }
+    const script = document.getElementById('ember_responsive_image_meta');
+    assert(
+      'No script tag found containing meta data for ember-responsive-image',
+      script
+    );
+    return JSON.parse(script.textContent);
+  }
+  set meta(meta) {
+    this._meta = meta;
+  }
 }
diff --git a/app/initializers/responsive-meta.js b/app/initializers/responsive-meta.js
deleted file mode 100644
index 6bfbc75c..00000000
--- a/app/initializers/responsive-meta.js
+++ /dev/null
@@ -1,4 +0,0 @@
-export {
-  default,
-  initialize,
-} from 'ember-responsive-image/initializers/responsive-meta';
diff --git a/fastboot/instance-initializers/responsive-meta.js b/fastboot/instance-initializers/responsive-meta.js
deleted file mode 100644
index 3f3e1b38..00000000
--- a/fastboot/instance-initializers/responsive-meta.js
+++ /dev/null
@@ -1,13 +0,0 @@
-import ResponsiveImage from 'ember-responsive-image/services/responsive-image';
-
-export function initialize(/* appInstance */) {
-  let meta = '__ember_responsive_image_meta__';
-  ResponsiveImage.reopen({
-    meta,
-  });
-}
-
-export default {
-  name: 'responsive-meta',
-  initialize,
-};
diff --git a/index.js b/index.js
index f531c55d..6ffe7e1b 100644
--- a/index.js
+++ b/index.js
@@ -1,10 +1,9 @@
 'use strict';
 const path = require('path');
 const Funnel = require('broccoli-funnel');
-const Writer = require('./broccoli-image-writer');
+const Writer = require('./lib/image-writer');
 const fs = require('fs-extra');
 const map = require('broccoli-stew').map;
-const find = require('broccoli-stew').find;
 const mergeTrees = require('broccoli-merge-trees');
 
 function defaultConfig() {
@@ -152,6 +151,7 @@ module.exports = {
   included(app, parentAddon) {
     this._super.included.apply(this, arguments);
     this.app = parentAddon || app;
+    this.processingTree = this.createProcessingTree();
   },
 
   config(env, baseConfig) {
@@ -192,41 +192,53 @@ module.exports = {
   },
 
   contentFor(type) {
+    // we write our image meta data as a script tag into the app's index.html, which the service will read from
+    // (that happens only in the browser, where we have easy access to the DOM. For FastBoot this is different, see below)
     if (type === 'head-footer') {
-      let txt = [
+      return [
         '<script id="ember_responsive_image_meta" type="application/json">',
-        "'__ember_responsive_image_meta__'",
+        JSON.stringify(this.extendMetadata()),
         '</script>',
-      ];
-      return txt.join('\n');
+      ].join('\n');
     }
   },
 
-  postprocessTree(type, tree) {
-    if (type === 'all') {
-      this.metaData.prepend = '';
-      if (this.app && this.app.options && this.app.options.fingerprint) {
-        this.metaData.prepend = this.app.options.fingerprint.prepend;
+  treeForFastBoot() {
+    // we have to rename our own fastboot tree so that our dummy app works correctly, due to this bug in ember-cli-fastboot:
+    // https://github.com/ember-fastboot/ember-cli-fastboot/issues/807
+    const tree = this.treeGenerator(path.join(__dirname, '-fastboot'));
+    const pattern = /["']__ember_responsive_image_meta__["']/;
+    // we replace our placeholder token with the image meta data generated from our broccoli plugin, so the meta data
+    // is part of the FastBoot specific service within the generated fastboot.js bundle. See `-fastboot/services/responsive-image.js`
+    const mapMeta = (content) =>
+      content.replace(pattern, JSON.stringify(this.extendMetadata()));
+
+    // This is some ugly hack to make sure the image processing happened before the FastBoot tree is emitted, as we need
+    // its meta data here. This is normally not the case, due to the timing of these hooks.
+    // So we merge the processingTree into our fastboot tree, and remove it later, to make sure it gets consumed.
+    return new Funnel(
+      map(mergeTrees([tree, this.processingTree]), '**/*.js', mapMeta),
+      {
+        include: ['**/*.js'],
       }
-      let trees = [];
-      this.addonOptions.forEach((options) => {
-        let imageTree = this.resizeImages(tree, options);
-        trees.push(imageTree);
-      });
-
-      let pattern = /["']__ember_responsive_image_meta__["']/;
-      let mapMeta = (content) =>
-        content.replace(pattern, JSON.stringify(this.extendMetadata()));
-
-      trees = trees.concat([
-        tree,
-        map(find(tree, '**/*.js'), mapMeta),
-        map(find(tree, '**/index.html'), mapMeta),
-      ]);
-      return mergeTrees(trees, { overwrite: true });
+    );
+  },
+
+  treeForPublic() {
+    return this.processingTree;
+  },
+
+  createProcessingTree() {
+    const tree = this._findHost().trees.public;
+    this.metaData.prepend = '';
+    if (this.app && this.app.options && this.app.options.fingerprint) {
+      this.metaData.prepend = this.app.options.fingerprint.prepend;
     }
+    const trees = this.addonOptions.map((options) => {
+      return this.resizeImages(tree, options);
+    });
 
-    return tree;
+    return mergeTrees(trees, { overwrite: true });
   },
 
   postBuild(result) {


Test patch:
diff --git a/addon-test-support/index.js b/addon-test-support/index.js
deleted file mode 100644
index 935fc40b..00000000
--- a/addon-test-support/index.js
+++ /dev/null
@@ -1,5 +0,0 @@
-import { initialize } from 'ember-responsive-image/initializers/responsive-meta';
-
-export function setupResponsiveImage(hooks = self) {
-  hooks.beforeEach(initialize);
-}
diff --git a/broccoli-image-writer.js b/lib/image-writer.js
similarity index 100%
rename from broccoli-image-writer.js
rename to lib/image-writer.js
diff --git a/tests/integration/components/responsive-image-test.js b/tests/integration/components/responsive-image-test.js
index 1827db31..06228342 100644
--- a/tests/integration/components/responsive-image-test.js
+++ b/tests/integration/components/responsive-image-test.js
@@ -1,12 +1,10 @@
 import { find, render } from '@ember/test-helpers';
-import { setupResponsiveImage } from 'ember-responsive-image/test-support';
 import { setupRenderingTest } from 'ember-qunit';
 import hbs from 'htmlbars-inline-precompile';
 import { test, module } from 'qunit';
 
 module('Integration: Responsive Image Component', function (hooks) {
   setupRenderingTest(hooks);
-  setupResponsiveImage(hooks);
 
   test('it renders the correct sourceset', async function (assert) {
     await render(hbs`<ResponsiveImage @image="test.png"/>`);