Repository: ember-cli/ember-cli
Pull request #7799 (2018-04-30 04:58:08)

Move processing of internal files
This change removes two private methods from `ember-app`:

+ `_configReplacePatterns` which was used to create a list of patterns
to replace different constants in internal files
+ `_processedEmberCLITree` which handled creation and processing of
internal `ember-cli` tree


Patch:
diff --git a/lib/broccoli/default-packager.js b/lib/broccoli/default-packager.js
index 634c5596f..2746bf33b 100644
--- a/lib/broccoli/default-packager.js
+++ b/lib/broccoli/default-packager.js
@@ -6,6 +6,9 @@ const concat = require('broccoli-concat');
 const Funnel = require('broccoli-funnel');
 const mergeTrees = require('./merge-trees');
 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 addonProcessTree = require('../utilities/addon-process-tree');
 
 const preprocessCss = p.preprocessCss;
@@ -14,6 +17,21 @@ const preprocessTemplates = p.preprocessTemplates;
 
 const DEFAULT_BOWER_PATH = 'bower_components';
 const DEFAULT_VENDOR_PATH = 'vendor';
+const EMBER_CLI_INTERNAL_FILES_PATH = '/vendor/ember-cli/';
+const EMBER_CLI_FILES = [
+  'app-boot.js',
+  'app-config.js',
+  'app-prefix.js',
+  'app-suffix.js',
+  'test-support-prefix.js',
+  'test-support-suffix.js',
+  'tests-prefix.js',
+  'tests-suffix.js',
+  'vendor-prefix.js',
+  'vendor-suffix.js',
+];
+
+const configReplacePatterns = emberAppUtils.configReplacePatterns;
 
 function callAddonsPreprocessTreeHook(project, type, tree) {
   return addonProcessTree(project, 'preprocessTree', type, tree);
@@ -92,16 +110,78 @@ module.exports = class DefaultPackager {
     this._cachedProcessedSrc = null;
     this._cachedProcessedTemplates = null;
     this._cachedProcessedJavascript = null;
+    this._cachedEmberCliInternalTree = null;
 
     this.options = options || {};
 
     this.env = this.options.env;
     this.name = this.options.name;
+    this.autoRun = this.options.autoRun;
     this.project = this.options.project;
     this.registry = this.options.registry;
     this.sourcemaps = this.options.sourcemaps;
     this.distPaths = this.options.distPaths;
+    this.areTestsEnabled = this.options.areTestsEnabled;
     this.scriptOutputFiles = this.options.scriptOutputFiles;
+    this.storeConfigInMeta = this.options.storeConfigInMeta;
+    this.isModuleUnificationEnabled = this.options.isModuleUnificationEnabled;
+  }
+
+  /*
+   * Returns a single tree with `ember-cli` internal files with the following
+   * structure:
+   *
+   * ```
+   * vendor/
+   * └── ember-cli
+   *     ├── app-boot.js
+   *     ├── app-config.js
+   *     ├── app-prefix.js
+   *     ├── app-suffix.js
+   *     ├── test-support-suffix.js
+   *     ├── test-support-prefix.js
+   *     ├── tests-prefix.js
+   *     ├── tests-suffix.js
+   *     ├── vendor-prefix.js
+   *     └── vendor-suffix.js
+   * ```
+   *
+   * Note, that the contents of these files is being matched against several
+   * internal `ember-cli` variables, such as:
+   *
+   * + `{{MODULE_PREFIX}}`
+   * + different types of `{{content-for}}` (`{{content-for 'app-boot'}}`)
+   *
+   * @private
+   * @method packageEmberCliInternalFiles
+   * @return {BroccoliTree}
+  */
+  packageEmberCliInternalFiles() {
+    if (this._cachedEmberCliInternalTree === null) {
+      let patterns = configReplacePatterns({
+        addons: this.project.addons,
+        autoRun: this.autoRun,
+        storeConfigInMeta: this.storeConfigInMeta,
+        isModuleUnification: this.isModuleUnificationEnabled,
+      });
+
+      let configTree = this.packageConfig(this.areTestsEnabled);
+      let configPath = path.join(this.name, 'config', 'environments', `${this.env}.json`);
+
+      let emberCLITree = new ConfigReplace(new UnwatchedDir(__dirname), configTree, {
+        configPath,
+        files: EMBER_CLI_FILES,
+        patterns,
+      });
+
+      this._cachedEmberCliInternalTree = new Funnel(emberCLITree, {
+        files: EMBER_CLI_FILES,
+        destDir: EMBER_CLI_INTERNAL_FILES_PATH,
+        annotation: 'Packaged Ember CLI Internal Files',
+      });
+    }
+
+    return this._cachedEmberCliInternalTree;
   }
 
   /*
diff --git a/lib/broccoli/ember-app.js b/lib/broccoli/ember-app.js
index 38f8d41ba..04f189bae 100644
--- a/lib/broccoli/ember-app.js
+++ b/lib/broccoli/ember-app.js
@@ -45,9 +45,7 @@ const processModulesOnly = require('./babel-process-modules-only');
 const semver = require('semver');
 const DefaultPackager = require('./default-packager');
 
-const normalizeUrl = emberAppUtils.normalizeUrl;
-const convertObjectToString = emberAppUtils.convertObjectToString;
-const contentFor = emberAppUtils.contentFor;
+const configReplacePatterns = emberAppUtils.configReplacePatterns;
 
 let DEFAULT_CONFIG = {
   storeConfigInMeta: true,
@@ -162,7 +160,11 @@ class EmberApp {
       project: this.project,
       registry: this.registry,
       sourcemaps: this.options.sourcemaps,
+      areTestsEnabled: this.tests,
       scriptOutputFiles: this._scriptOutputFiles,
+      autoRun: this.options.autoRun,
+      storeConfigInMeta: this.options.storeConfigInMeta,
+      isModuleUnificationEnabled: experiments.MODULE_UNIFICATION && !!this.trees.src,
       distPaths: {
         appJsFile: this.options.outputPaths.app.js,
         appCssFile: this.options.outputPaths.app.css,
@@ -864,10 +866,17 @@ class EmberApp {
       index = mergeTrees([appIndex, srcIndex], { overwrite: true });
     }
 
+    let patterns = configReplacePatterns({
+      addons: this.project.addons,
+      autoRun: this.options.autoRun,
+      storeConfigInMeta: this.options.storeConfigInMeta,
+      isModuleUnification: experiments.MODULE_UNIFICATION && !!this.trees.src,
+    });
+
     return new ConfigReplace(index, this._defaultPackager.packageConfig(this.tests), {
       configPath: path.join(this.name, 'config', 'environments', `${this.env}.json`),
       files: [htmlName],
-      patterns: this._configReplacePatterns(),
+      patterns,
     });
   }
 
@@ -923,42 +932,6 @@ class EmberApp {
     return this._cachedTemplateTree;
   }
 
-  /**
-    @private
-    @method _configReplacePatterns
-    @return
-  */
-  _configReplacePatterns() {
-    let contentForOptions = {
-      addons: this.project.addons,
-      autoRun: this.options.autoRun,
-      storeConfigInMeta: this.options.storeConfigInMeta,
-      isModuleUnification: experiments.MODULE_UNIFICATION && !!this.trees.src,
-    };
-
-    return [{
-      match: /{{rootURL}}/g,
-      replacement(config) {
-        return normalizeUrl(config.rootURL);
-      },
-    }, {
-      match: /{{EMBER_ENV}}/g,
-      replacement(config) {
-        return convertObjectToString(config.EmberENV);
-      },
-    }, {
-      match: /{{content-for ['"](.+)["']}}/g,
-      replacement(config, match, type) {
-        return contentFor(config, match, type, contentForOptions);
-      },
-    }, {
-      match: /{{MODULE_PREFIX}}/g,
-      replacement(config) {
-        return config.modulePrefix;
-      },
-    }];
-  }
-
   /**
     Returns the tree for /tests/index.html
 
@@ -974,11 +947,18 @@ class EmberApp {
       annotation: 'Funnel (test index)',
     });
 
+    let patterns = configReplacePatterns({
+      addons: this.project.addons,
+      autoRun: this.options.autoRun,
+      storeConfigInMeta: this.options.storeConfigInMeta,
+      isModuleUnification: experiments.MODULE_UNIFICATION && !!this.trees.src,
+    });
+
     return new ConfigReplace(index, this._defaultPackager.packageConfig(this.tests), {
       configPath: path.join(this.name, 'config', 'environments', 'test.json'),
       files: ['tests/index.html'],
       env: 'test',
-      patterns: this._configReplacePatterns(),
+      patterns,
     });
   }
 
@@ -1293,43 +1273,6 @@ class EmberApp {
     return this._cachedExternalTree;
   }
 
-  /**
-    @private
-    @method _processedEmberCLITree
-    @return
-  */
-  _processedEmberCLITree() {
-    if (!this._cachedEmberCLITree) {
-      let files = [
-        'vendor-prefix.js',
-        'vendor-suffix.js',
-        'app-prefix.js',
-        'app-suffix.js',
-        'app-config.js',
-        'app-boot.js',
-        'test-support-prefix.js',
-        'test-support-suffix.js',
-        'tests-prefix.js',
-        'tests-suffix.js',
-      ];
-      let emberCLITree = new ConfigReplace(new UnwatchedDir(__dirname), this._defaultPackager.packageConfig(this.tests), {
-        configPath: path.join(this.name, 'config', 'environments', `${this.env}.json`),
-        files,
-
-        patterns: this._configReplacePatterns(),
-      });
-
-      this._cachedEmberCLITree = new Funnel(emberCLITree, {
-        files,
-        srcDir: '/',
-        destDir: '/vendor/ember-cli/',
-        annotation: 'Funnel (ember-cli-tree)',
-      });
-    }
-
-    return this._cachedEmberCLITree;
-  }
-
   /**
     @private
     @method _testAppConfigTree
@@ -1338,12 +1281,17 @@ class EmberApp {
   _testAppConfigTree() {
     if (!this._cachedTestAppConfigTree) {
       let files = ['app-config.js'];
+      let patterns = configReplacePatterns({
+        addons: this.project.addons,
+        autoRun: this.options.autoRun,
+        storeConfigInMeta: this.options.storeConfigInMeta,
+        isModuleUnification: experiments.MODULE_UNIFICATION && !!this.trees.src,
+      });
 
       let emberCLITree = new ConfigReplace(new UnwatchedDir(__dirname), this._defaultPackager.packageConfig(this.tests), {
         configPath: path.join(this.name, 'config', 'environments', `test.json`),
         files,
-
-        patterns: this._configReplacePatterns(),
+        patterns,
       });
 
       this._cachedTestAppConfigTree = new Funnel(emberCLITree, {
@@ -1380,9 +1328,8 @@ class EmberApp {
     });
 
     let external = this._processedExternalTree();
-
-    let emberCLITree = this._processedEmberCLITree();
     let postprocessedApp = this._defaultPackager.processJavascript(mergedTree);
+    let emberCLITree = this._defaultPackager.packageEmberCliInternalFiles();
 
     let sourceTrees = [
       external,
@@ -1421,7 +1368,7 @@ class EmberApp {
   appTests(coreTestTree) {
     let appTestTrees = [].concat(
       this.hinting && this.lintTestTrees(),
-      this._processedEmberCLITree(),
+      this._defaultPackager.packageEmberCliInternalFiles(),
       this._testAppConfigTree(),
       coreTestTree
     ).filter(Boolean);
@@ -1604,7 +1551,7 @@ class EmberApp {
     testSupportPath = testSupportPath.testSupport || testSupportPath;
 
     let external = this._processedExternalTree();
-    let emberCLITree = this._processedEmberCLITree();
+    let emberCLITree = this._defaultPackager.packageEmberCliInternalFiles();
     let addonTestSupportTree = this.addonTestSupportTree();
 
     let headerFiles = [].concat(
diff --git a/lib/utilities/ember-app-utils.js b/lib/utilities/ember-app-utils.js
index d3d0c6f5a..703fc6bcd 100644
--- a/lib/utilities/ember-app-utils.js
+++ b/lib/utilities/ember-app-utils.js
@@ -143,4 +143,43 @@ function contentFor(config, match, type, options) {
   return content.join('\n');
 }
 
-module.exports = { normalizeUrl, convertObjectToString, calculateBaseTag, contentFor };
+/*
+ * Return a list of pairs: a pattern to match to a replacement function.
+ *
+ * Used to replace various tags in `index.html` and `tests/index.html`.
+ *
+ * @param {Object} options
+ * @param {Array} options.addons A list of project's add-ons
+ * @param {Boolean} options.autoRun Controls whether to bootstrap the
+                    application or not
+ * @param {Boolean} options.storeConfigInMeta Controls whether to include the
+                    contents of config
+ * @param {Boolean} options.isModuleUnification Signifies if the application
+                    supports module unification or not
+   @return {Array} An array of patterns to match against and replace
+*/
+function configReplacePatterns(options) {
+  return [{
+    match: /{{rootURL}}/g,
+    replacement(config) {
+      return normalizeUrl(config.rootURL);
+    },
+  }, {
+    match: /{{EMBER_ENV}}/g,
+    replacement(config) {
+      return convertObjectToString(config.EmberENV);
+    },
+  }, {
+    match: /{{content-for ['"](.+)["']}}/g,
+    replacement(config, match, type) {
+      return contentFor(config, match, type, options);
+    },
+  }, {
+    match: /{{MODULE_PREFIX}}/g,
+    replacement(config) {
+      return config.modulePrefix;
+    },
+  }];
+}
+
+module.exports = { normalizeUrl, convertObjectToString, calculateBaseTag, contentFor, configReplacePatterns };


Test patch:
diff --git a/tests/unit/broccoli/default-packager/ember-cli-internal-test.js b/tests/unit/broccoli/default-packager/ember-cli-internal-test.js
new file mode 100644
index 000000000..cba3e5999
--- /dev/null
+++ b/tests/unit/broccoli/default-packager/ember-cli-internal-test.js
@@ -0,0 +1,217 @@
+'use strict';
+
+const co = require('co');
+const expect = require('chai').expect;
+const DefaultPackager = require('../../../../lib/broccoli/default-packager');
+const broccoliTestHelper = require('broccoli-test-helper');
+
+const buildOutput = broccoliTestHelper.buildOutput;
+const createTempDir = broccoliTestHelper.createTempDir;
+
+describe('Default Packager: Ember CLI Internal', function() {
+  let input, output;
+
+  let CONFIG = {
+    config: {
+      'environment.js': '',
+    },
+  };
+  let project = {
+    addons: [],
+
+    configPath() {
+      return `${input.path()}/config/environment`;
+    },
+
+    config() {
+      return {
+        modulePrefix: 'the-best-app-ever',
+      };
+    },
+  };
+
+  before(co.wrap(function *() {
+    input = yield createTempDir();
+
+    input.write(CONFIG);
+  }));
+
+  after(co.wrap(function *() {
+    yield input.dispose();
+  }));
+
+  afterEach(co.wrap(function *() {
+    yield output.dispose();
+  }));
+
+  it('caches packaged ember cli internal tree', co.wrap(function *() {
+    let defaultPackager = new DefaultPackager({
+      env: 'development',
+      name: 'the-best-app-ever',
+      project,
+      autoRun: false,
+      areTestsEnabled: true,
+      storeConfigInMeta: false,
+      isModuleUnificationEnabled: false,
+    });
+
+    expect(defaultPackager._cachedEmberCliInternalTree).to.equal(null);
+
+    output = yield buildOutput(defaultPackager.packageEmberCliInternalFiles());
+
+    expect(defaultPackager._cachedEmberCliInternalTree).to.not.equal(null);
+    expect(defaultPackager._cachedEmberCliInternalTree._annotation).to.equal('Packaged Ember CLI Internal Files');
+  }));
+
+  it('packages internal files properly', co.wrap(function *() {
+    let defaultPackager = new DefaultPackager({
+      env: 'development',
+      name: 'the-best-app-ever',
+      project,
+      autoRun: false,
+      areTestsEnabled: true,
+      storeConfigInMeta: false,
+      isModuleUnificationEnabled: false,
+    });
+
+    expect(defaultPackager._cachedEmberCliInternalTree).to.equal(null);
+
+    output = yield buildOutput(defaultPackager.packageEmberCliInternalFiles());
+
+    let outputFiles = output.read();
+
+    let emberCliFiles = outputFiles.vendor['ember-cli'];
+
+    expect(Object.keys(emberCliFiles)).to.deep.equal([
+      'app-boot.js',
+      'app-config.js',
+      'app-prefix.js',
+      'app-suffix.js',
+      'test-support-prefix.js',
+      'test-support-suffix.js',
+      'tests-prefix.js',
+      'tests-suffix.js',
+      'vendor-prefix.js',
+      'vendor-suffix.js',
+    ]);
+  }));
+
+  it('populates the contents of internal files correctly', co.wrap(function *() {
+    let defaultPackager = new DefaultPackager({
+      env: 'development',
+      name: 'the-best-app-ever',
+      project,
+      autoRun: false,
+      areTestsEnabled: false,
+      storeConfigInMeta: false,
+      isModuleUnificationEnabled: false,
+    });
+
+    expect(defaultPackager._cachedEmberCliInternalTree).to.equal(null);
+
+    output = yield buildOutput(defaultPackager.packageEmberCliInternalFiles());
+
+    let outputFiles = output.read();
+
+    let emberCliFiles = outputFiles.vendor['ember-cli'];
+
+    let appBootFileContent = emberCliFiles['app-boot.js'].trim();
+    let appConfigFileContent = emberCliFiles['app-config.js'].trim();
+    let appPrefixFileContent = emberCliFiles['app-prefix.js'].trim();
+    let appSuffixFileContent = emberCliFiles['app-suffix.js'].trim();
+
+    let testPrefixFileContent = emberCliFiles['tests-prefix.js'].trim();
+    let testSuffixFileContent = emberCliFiles['tests-suffix.js'].trim();
+    let testSupportPrefixFileContent = emberCliFiles['test-support-prefix.js'].trim();
+    let testSupportSuffixFileContent = emberCliFiles['test-support-suffix.js'].trim();
+
+    let vendorPrefixFileContent = emberCliFiles['vendor-prefix.js'].trim();
+    let vendorSuffixFileContent = emberCliFiles['vendor-suffix.js'].trim();
+
+    expect(appBootFileContent).to.equal('');
+    expect(appConfigFileContent).to.contain(`'default': {"modulePrefix":"the-best-app-ever"}`);
+    expect(appPrefixFileContent).to.contain(`'use strict';`);
+    expect(appSuffixFileContent).to.equal('');
+
+    expect(testPrefixFileContent).to.contain(`'use strict';`);
+    expect(testSuffixFileContent).to.contain(`require('the-best-app-ever/tests/test-helper');\nEmberENV.TESTS_FILE_LOADED = true;`);
+    expect(testSupportPrefixFileContent).to.equal('');
+    expect(testSupportSuffixFileContent).to.contain('runningTests = true;\n\nif (window.Testem) {\n  window.Testem.hookIntoTestFramework();\n}');
+
+    expect(vendorPrefixFileContent).to.contain('window.EmberENV = {};\nvar runningTests = false;');
+    expect(vendorSuffixFileContent).to.equal('');
+  }));
+
+  it('populates the contents of internal files correctly when `storeConfigInMeta` is enabled', co.wrap(function *() {
+    let defaultPackager = new DefaultPackager({
+      env: 'development',
+      name: 'the-best-app-ever',
+      project,
+      autoRun: false,
+      areTestsEnabled: true,
+      storeConfigInMeta: true,
+      isModuleUnificationEnabled: false,
+    });
+
+    expect(defaultPackager._cachedEmberCliInternalTree).to.equal(null);
+
+    output = yield buildOutput(defaultPackager.packageEmberCliInternalFiles());
+
+    let outputFiles = output.read();
+
+    let emberCliFiles = outputFiles.vendor['ember-cli'];
+    let appConfigFileContent = emberCliFiles['app-config.js'].trim();
+
+    expect(appConfigFileContent).to.contain(`var rawConfig = document.querySelector(`);
+    expect(appConfigFileContent).to.contain(`var config = JSON.parse(unescape(rawConfig));`);
+  }));
+
+  it('populates the contents of internal files correctly, including content from add-ons', co.wrap(function *() {
+    let defaultPackager = new DefaultPackager({
+      env: 'development',
+      name: 'the-best-app-ever',
+      project: {
+        addons: [{
+          contentFor(type) {
+            if (type === 'app-prefix') {
+              return 'CUSTOM APP PREFIX CODE';
+            }
+          },
+        }, {
+          contentFor(type) {
+            if (type === 'app-boot') {
+              return 'CUSTOM APP BOOT CODE';
+            }
+          },
+        }],
+
+        configPath() {
+          return `${input.path()}/config/environment`;
+        },
+
+        config() {
+          return {
+            modulePrefix: 'the-best-app-ever',
+          };
+        },
+      },
+      autoRun: false,
+      areTestsEnabled: true,
+      storeConfigInMeta: false,
+      isModuleUnificationEnabled: false,
+    });
+
+    expect(defaultPackager._cachedEmberCliInternalTree).to.equal(null);
+
+    output = yield buildOutput(defaultPackager.packageEmberCliInternalFiles());
+
+    let outputFiles = output.read();
+
+    let emberCliFiles = outputFiles.vendor['ember-cli'];
+    let appBootFileContent = emberCliFiles['app-boot.js'].trim();
+    let appPrefixFileContent = emberCliFiles['app-prefix.js'].trim();
+
+    expect(appBootFileContent).to.equal('CUSTOM APP BOOT CODE');
+    expect(appPrefixFileContent).to.equal(`'use strict';\n\nCUSTOM APP PREFIX CODE`);
+  }));
+});