Repository: ember-cli/ember-cli
Pull request #7821 (2018-05-09 04:31:11)

Move additional assets import


Patch:
diff --git a/lib/broccoli/default-packager.js b/lib/broccoli/default-packager.js
index 283f39d7e..98bdf6b63 100644
--- a/lib/broccoli/default-packager.js
+++ b/lib/broccoli/default-packager.js
@@ -9,6 +9,7 @@ const ConfigLoader = require('broccoli-config-loader');
 const UnwatchedDir = require('broccoli-source').UnwatchedDir;
 const ConfigReplace = require('broccoli-config-replace');
 const emberAppUtils = require('../utilities/ember-app-utils');
+const funnelReducer = require('broccoli-funnel-reducer');
 const addonProcessTree = require('../utilities/addon-process-tree');
 
 const preprocessCss = p.preprocessCss;
@@ -114,6 +115,7 @@ module.exports = class DefaultPackager {
     this._cachedProcessedTemplates = null;
     this._cachedProcessedJavascript = null;
     this._cachedEmberCliInternalTree = null;
+    this._cachedProcessedAdditionalAssets = null;
     this._cachedProcessedAppAndDependencies = null;
 
     this.options = options || {};
@@ -131,6 +133,7 @@ module.exports = class DefaultPackager {
     this.scriptOutputFiles = this.options.scriptOutputFiles;
     this.storeConfigInMeta = this.options.storeConfigInMeta;
     this.customTransformsMap = this.options.customTransformsMap;
+    this.additionalAssetPaths = this.options.additionalAssetPaths;
     this.isModuleUnificationEnabled = this.options.isModuleUnificationEnabled;
   }
 
@@ -263,6 +266,42 @@ module.exports = class DefaultPackager {
     return this._cachedProcessedAppAndDependencies;
   }
 
+  /*
+   * Adds additional assets to the results tree, given the following list:
+   *
+   * ```
+   * [{
+   *   src: 'vendor/font-awesome/fonts',
+   *   file: 'FontAwesome.otf',
+   *   dest: 'fonts'
+   * }]
+   * ```
+   *
+   * where `src` is a source path, `file` is a file name, and `dest` is a new
+   * destination.
+   *
+   * @private
+   * @method importAdditionalAssets
+   * @param {BroccoliTree}
+   * @return {BroccoliTree}
+  */
+  importAdditionalAssets(tree) {
+    if (this._cachedProcessedAdditionalAssets === null) {
+      let otherAssetTrees = funnelReducer(this.additionalAssetPaths).map(options => {
+        let files = options.include.join(',');
+        options.annotation = `${options.srcDir}/{${files}} => ${options.destDir}/{${files}}`;
+
+        return new Funnel(tree, options);
+      });
+
+      this._cachedProcessedAdditionalAssets = mergeTrees(otherAssetTrees, {
+        annotation: 'Processed Additional Assets',
+      });
+    }
+
+    return this._cachedProcessedAdditionalAssets;
+  }
+
   /*
    * Runs all registered transformations on the passed in tree and returns the
    * result.
diff --git a/lib/broccoli/ember-app.js b/lib/broccoli/ember-app.js
index ed0e16511..42450c6e3 100644
--- a/lib/broccoli/ember-app.js
+++ b/lib/broccoli/ember-app.js
@@ -32,7 +32,6 @@ const defaultsDeep = require('ember-cli-lodash-subset').defaultsDeep;
 const omitBy = require('ember-cli-lodash-subset').omitBy;
 const isNull = require('ember-cli-lodash-subset').isNull;
 const Funnel = require('broccoli-funnel');
-const funnelReducer = require('broccoli-funnel-reducer');
 const logger = require('heimdalljs-logger')('ember-cli:ember-app');
 const emberAppUtils = require('../utilities/ember-app-utils');
 const addonProcessTree = require('../utilities/addon-process-tree');
@@ -165,6 +164,7 @@ class EmberApp {
       customTransformsMap: this._customTransformsMap,
       storeConfigInMeta: this.options.storeConfigInMeta,
       isModuleUnificationEnabled: experiments.MODULE_UNIFICATION && !!this.trees.src,
+      additionalAssetPaths: this.otherAssetPaths,
       distPaths: {
         appJsFile: this.options.outputPaths.app.js,
         appCssFile: this.options.outputPaths.app.css,
@@ -1461,31 +1461,6 @@ class EmberApp {
     });
   }
 
-  /**
-    Returns the tree for the additional assets which are not in
-    one of the default trees.
-
-    @private
-    @method otherAssets
-    @return {Tree} Merged tree for other assets
-   */
-  otherAssets() {
-    let external = this.getExternalTree();
-    // combine obviously shared funnels.
-    let otherAssetTrees = funnelReducer(this.otherAssetPaths).map(options => {
-      options.annotation = `Funnel
-    ${options.srcDir}
-    ${options.destDir}
-   include:${options.include.length}`;
-
-      return new Funnel(external, options);
-    });
-
-    return mergeTrees(otherAssetTrees, {
-      annotation: 'TreeMerger (otherAssetTrees)',
-    });
-  }
-
   /**
     @public
     @method dependencies
@@ -1692,7 +1667,7 @@ class EmberApp {
       this._defaultPackager.processIndex(fullTree),
       javascriptTree,
       stylesTree,
-      this.otherAssets(),
+      this._defaultPackager.importAdditionalAssets(fullTree),
       publicTree,
     ].filter(Boolean);
 


Test patch:
diff --git a/tests/unit/broccoli/default-packager/additional-assets-test.js b/tests/unit/broccoli/default-packager/additional-assets-test.js
new file mode 100644
index 000000000..cbb36f374
--- /dev/null
+++ b/tests/unit/broccoli/default-packager/additional-assets-test.js
@@ -0,0 +1,161 @@
+'use strict';
+
+const co = require('co');
+const expect = require('chai').expect;
+const Funnel = require('broccoli-funnel');
+const DefaultPackager = require('../../../../lib/broccoli/default-packager');
+const broccoliTestHelper = require('broccoli-test-helper');
+const defaultPackagerHelpers = require('../../../helpers/default-packager');
+
+const buildOutput = broccoliTestHelper.buildOutput;
+const createTempDir = broccoliTestHelper.createTempDir;
+const setupRegistryFor = defaultPackagerHelpers.setupRegistryFor;
+
+describe('Default Packager: Additional Assets', function() {
+  let input, output;
+
+  let MODULES = {
+    'addon-tree-output': { },
+    'the-best-app-ever': {
+      'router.js': 'router.js',
+      'app.js': 'app.js',
+      'components': {
+        'x-foo.js': 'export default class {}',
+      },
+      'routes': {
+        'application.js': 'export default class {}',
+      },
+      'config': {
+        'environment.js': 'environment.js',
+      },
+      'templates': {},
+    },
+    'vendor': {
+      'font-awesome': {
+        fonts: {
+          'FontAwesome.otf': '',
+          'FontAwesome.woff': '',
+        },
+      },
+    },
+  };
+  let project = {
+    configPath() {
+      return `${input.path()}/the-best-app-ever/config/environment`;
+    },
+
+    config() {
+      return { a: 1 };
+    },
+
+    registry: setupRegistryFor('template', function(tree) {
+      return new Funnel(tree, {
+        getDestinationPath(relativePath) {
+          return relativePath.replace(/hbs$/g, 'js');
+        },
+      });
+    }),
+
+    addons: [],
+  };
+
+  before(co.wrap(function *() {
+    input = yield createTempDir();
+
+    input.write(MODULES);
+  }));
+
+  after(co.wrap(function *() {
+    yield input.dispose();
+  }));
+
+  afterEach(co.wrap(function *() {
+    yield output.dispose();
+  }));
+
+  it('caches packaged javascript tree', co.wrap(function *() {
+    let defaultPackager = new DefaultPackager({
+      name: 'the-best-app-ever',
+      env: 'development',
+
+      distPaths: {
+        appJsFile: '/assets/the-best-app-ever.js',
+        vendorJsFile: '/assets/vendor.js',
+      },
+
+      registry: setupRegistryFor('template', function(tree) {
+        return new Funnel(tree, {
+          getDestinationPath(relativePath) {
+            return relativePath.replace(/hbs$/g, 'js');
+          },
+        });
+      }),
+
+      additionalAssetPaths: [{
+        src: 'vendor/font-awesome/fonts',
+        file: 'FontAwesome.otf',
+        dest: 'fonts',
+      }, {
+        src: 'vendor/font-awesome/fonts',
+        file: 'FontAwesome.woff',
+        dest: 'fonts',
+      }],
+
+      project,
+    });
+
+    expect(defaultPackager._cachedProcessedAdditionalAssets).to.equal(null);
+
+    output = yield buildOutput(defaultPackager.importAdditionalAssets(input.path()));
+
+    expect(defaultPackager._cachedProcessedAdditionalAssets).to.not.equal(null);
+    expect(
+      defaultPackager._cachedProcessedAdditionalAssets._annotation
+    ).to.equal('vendor/font-awesome/fonts/{FontAwesome.otf,FontAwesome.woff} => fonts/{FontAwesome.otf,FontAwesome.woff}');
+  }));
+
+  it('imports additional assets properly', co.wrap(function *() {
+    let defaultPackager = new DefaultPackager({
+      name: 'the-best-app-ever',
+      env: 'development',
+
+      distPaths: {
+        appJsFile: '/assets/the-best-app-ever.js',
+        vendorJsFile: '/assets/vendor.js',
+      },
+
+      registry: setupRegistryFor('template', function(tree) {
+        return new Funnel(tree, {
+          getDestinationPath(relativePath) {
+            return relativePath.replace(/hbs$/g, 'js');
+          },
+        });
+      }),
+
+      additionalAssetPaths: [{
+        src: 'vendor/font-awesome/fonts',
+        file: 'FontAwesome.otf',
+        dest: 'fonts',
+      }, {
+        src: 'vendor/font-awesome/fonts',
+        file: 'FontAwesome.woff',
+        dest: 'fonts',
+      }],
+
+      project,
+    });
+
+    expect(defaultPackager._cachedProcessedAdditionalAssets).to.equal(null);
+
+    output = yield buildOutput(defaultPackager.importAdditionalAssets(input.path()));
+
+    let outputFiles = output.read();
+
+    expect(outputFiles).to.deep.equal({
+      fonts: {
+        'FontAwesome.otf': '',
+        'FontAwesome.woff': '',
+      },
+    });
+  }));
+});