Skip to content

Commit

Permalink
add delayed transpilation
Browse files Browse the repository at this point in the history
  • Loading branch information
Kelly Selden committed Jan 28, 2018
1 parent d293f0a commit 4670071
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 50 deletions.
46 changes: 46 additions & 0 deletions lib/broccoli/amd-excluder.js
@@ -0,0 +1,46 @@
'use strict';

const fs = require('fs');
const path = require('path');
const Funnel = require('broccoli-funnel');
const walkSync = require('walk-sync');

// Some addons do their own compilation, which means the addon trees will
// be a mix of ES6 and AMD files. This plugin gives us a way to separate the
// files, as we don't want to double compile the AMD code.

// It has a very simple method of detecting AMD code, because we only care
// about babel output, which is pretty consistent.
class AmdExcluder extends Funnel {
constructor(inputNode, options) {
super(inputNode, {
exclude: [],
annotation: options.annotation,
});

this.options = options;
}

build() {
let inputPath = this.inputPaths[0];

let files = walkSync(inputPath, {
directories: false,
globs: [`**/*.js`],
});

for (let file of files) {
let inputFilePath = path.join(inputPath, file);
let source = fs.readFileSync(inputFilePath, 'utf8');

// ember-data and others compile their own source
if (source.indexOf('define(') === 0) {
this.exclude.push(file);
}
}

super.build();
}
}

module.exports = AmdExcluder;
45 changes: 43 additions & 2 deletions lib/broccoli/ember-app.js
Expand Up @@ -40,11 +40,13 @@ const funnelReducer = require('broccoli-funnel-reducer');
const logger = require('heimdalljs-logger')('ember-cli:ember-app');
const addonProcessTree = require('../utilities/addon-process-tree');
const lintAddonsByType = require('../utilities/lint-addons-by-type');
const registryHasPreprocessor = require('../utilities/registry-has-preprocessor');
const experiments = require('../experiments');
const processModulesOnly = require('./babel-process-modules-only');
const semver = require('semver');
const Assembler = require('./assembler');
const strategies = require('./strategies');
const AmdExcluder = require('./amd-excluder');

const createVendorJsStrategy = strategies.createVendorJsStrategy;
const createApplicationJsStrategy = strategies.createApplicationJsStrategy;
Expand Down Expand Up @@ -617,6 +619,41 @@ class EmberApp {
}, []);
}

_compileAddonTemplates(tree) {
if (registryHasPreprocessor(this.registry, 'template')) {
tree = preprocessTemplates(tree, {
annotation: `_compileAddonTemplates`,
registry: this.registry,
});
}

return tree;
}

_compileAddonJs(tree) {
let precompiledSource = new AmdExcluder(tree, {
annotation: `_compileAddonJs`,
});

precompiledSource = preprocessJs(precompiledSource, '/', '/', {
annotation: `_compileAddonJs`,
registry: this.registry,
});

tree = mergeTrees([tree, precompiledSource], {
overwrite: true,
});

return tree;
}

_compileAddonTree(tree) {
tree = this._compileAddonTemplates(tree);
tree = this._compileAddonJs(tree);

return tree;
}

/**
Runs addon post-processing on a given tree and returns the processed tree.
Expand Down Expand Up @@ -1079,7 +1116,9 @@ class EmberApp {
annotation: 'TreeMerger: `addon/` trees',
});

this._cachedAddonTree = new Funnel(combinedAddonTree, {
let compiledAddonTree = this._compileAddonTree(combinedAddonTree);

this._cachedAddonTree = new Funnel(compiledAddonTree, {
destDir: 'addon-tree-output',
annotation: 'Funnel: addon-tree-output',
});
Expand Down Expand Up @@ -1532,7 +1571,9 @@ class EmberApp {
annotation: 'TreeMerger (addon-test-support)',
});

let finalAddonTestSupportTree = new Funnel(addonTestSupportTree, {
let compiledAddonTestSupportTree = this._compileAddonTree(addonTestSupportTree);

let finalAddonTestSupportTree = new Funnel(compiledAddonTestSupportTree, {
allowEmpty: true,
destDir: 'addon-test-support',
annotation: 'Funnel: Addon Test Support',
Expand Down
65 changes: 39 additions & 26 deletions lib/models/addon.js
Expand Up @@ -34,6 +34,7 @@ const addonProcessTree = require('../utilities/addon-process-tree');
const semver = require('semver');
const processModulesOnly = require('../broccoli/babel-process-modules-only');
const npa = require('npm-package-arg');
const registryHasPreprocessor = require('../utilities/registry-has-preprocessor');

const BUILD_BABEL_OPTIONS_FOR_PREPROCESSORS = Symbol('BUILD_BABEL_OPTIONS_FOR_PREPROCESSORS');

Expand Down Expand Up @@ -127,10 +128,6 @@ function warn(message) {
}
}

function registryHasPreprocessor(registry, type) {
return registry.load(type).length > 0;
}

/**
Root class for an Addon. If your addon module exports an Object this
will be extended from this base class. If you export a constructor (function),
Expand Down Expand Up @@ -227,6 +224,8 @@ let addonProto = {
this.__originalOptions[emberCLIBabelConfigKey] = this.options[emberCLIBabelConfigKey] = defaultsDeep(this.options[emberCLIBabelConfigKey], {
compileModules: true,
});

this.registry.remove('template', 'ember-cli-htmlbars');
},

/*
Expand Down Expand Up @@ -1017,18 +1016,23 @@ let addonProto = {
this._requireBuildPackages();

if (this.shouldCompileTemplates()) {
if (!registryHasPreprocessor(this.registry, 'template')) {
throw new SilentError(`Addon templates were detected, but there are no template compilers registered for \`${this.name}\`. ` +
`Please make sure your template precompiler (commonly \`ember-cli-htmlbars\`) is listed in \`dependencies\` ` +
`(NOT \`devDependencies\`) in \`${this.name}\`'s \`package.json\`.`);
}
// if (!registryHasPreprocessor(this.registry, 'template')) {
// throw new SilentError(`Addon templates were detected, but there are no template compilers registered for \`${this.name}\`. ` +
// `Please make sure your template precompiler (commonly \`ember-cli-htmlbars\`) is listed in \`dependencies\` ` +
// `(NOT \`devDependencies\`) in \`${this.name}\`'s \`package.json\`.`);
// }

let preprocessedTemplateTree = this._addonPreprocessTree('template', this._addonTemplateFiles(addonTree));

let processedTemplateTree = preprocessTemplates(preprocessedTemplateTree, {
annotation: `compileTemplates(${this.name})`,
registry: this.registry,
});
let processedTemplateTree;
if (registryHasPreprocessor(this.registry, 'template')) {
processedTemplateTree = preprocessTemplates(preprocessedTemplateTree, {
annotation: `compileTemplates(${this.name})`,
registry: this.registry,
});
} else {
processedTemplateTree = preprocessedTemplateTree;
}

let postprocessedTemplateTree = this._addonPostprocessTree('template', processedTemplateTree);

Expand Down Expand Up @@ -1069,18 +1073,6 @@ let addonProto = {
this.options.babel = this.__originalOptions.babel;
}

let emberCLIBabelConfigKey = this._emberCLIBabelConfigKey();
if (!this.options[emberCLIBabelConfigKey] || !this.options[emberCLIBabelConfigKey].compileModules) {
this._warn(
`Ember CLI addons manage their own module transpilation during the \`treeForAddon\` processing. ` +
`\`${this.name}\` (found at \`${this.root}\`) has overridden the \`this.options.${emberCLIBabelConfigKey}.compileModules\` ` +
`value which conflicts with the addons ability to transpile its \`addon/\` files properly.`
);

this.options[emberCLIBabelConfigKey] = this.options[emberCLIBabelConfigKey] || {};
this.options[emberCLIBabelConfigKey].compileModules = true;
}

let addonJs = this.processedAddonJsFiles(tree);
let templatesTree = this.compileTemplates(tree);

Expand Down Expand Up @@ -1186,7 +1178,28 @@ let addonProto = {
@return {Tree} Preprocessed javascript
*/
preprocessJs() {
return preprocessJs.apply(preprocessJs, arguments);
let emberCLIBabelConfigKey = this._emberCLIBabelConfigKey();

let original = this.options[emberCLIBabelConfigKey];

// the app will handle transpilation after it tree-shakes
// do it here instead of the constructor because
// ember-data and others do their own compilation in their
// treeForAddon without calling super
// they need the original params preserved because they call
// babel themselves and expect compilation the old way
this.options[emberCLIBabelConfigKey] = Object.assign({}, original, {
compileModules: false,
disablePresetEnv: true,
});

let tree = preprocessJs.apply(preprocessJs, arguments);

// return the original params because there are multiple
// entrances to preprocessJs
this.options[emberCLIBabelConfigKey] = original;

return tree;
},

/**
Expand Down
5 changes: 5 additions & 0 deletions lib/utilities/registry-has-preprocessor.js
@@ -0,0 +1,5 @@
'use strict';

module.exports = function registryHasPreprocessor(registry, type) {
return registry.load(type).length > 0;
};
44 changes: 22 additions & 22 deletions tests/unit/models/addon-test.js
Expand Up @@ -631,28 +631,28 @@ describe('models/addon.js', function() {
addon = findWhere(project.addons, { name: 'Ember CLI Generated with export' });
});

it('should throw a useful error if a template compiler is not present -- non-pods', function() {
addon.root = path.join(fixturePath, 'with-addon-templates');

expect(() => {
addon.compileTemplates();
}).to.throw(
`Addon templates were detected, but there are no template compilers registered for \`${addon.name}\`. ` +
`Please make sure your template precompiler (commonly \`ember-cli-htmlbars\`) is listed in \`dependencies\` ` +
`(NOT \`devDependencies\`) in \`${addon.name}\`'s \`package.json\`.`);
});

it('should throw a useful error if a template compiler is not present -- pods', function() {
addon.root = path.join(fixturePath, 'with-addon-pod-templates');

expect(() => {
addon.compileTemplates();
}).to.throw(
`Addon templates were detected, but there are no template compilers registered for \`${addon.name}\`. ` +
`Please make sure your template precompiler (commonly \`ember-cli-htmlbars\`) is listed in \`dependencies\` ` +
`(NOT \`devDependencies\`) in \`${addon.name}\`'s \`package.json\`.`
);
});
// it('should throw a useful error if a template compiler is not present -- non-pods', function() {
// addon.root = path.join(fixturePath, 'with-addon-templates');

// expect(() => {
// addon.compileTemplates();
// }).to.throw(
// `Addon templates were detected, but there are no template compilers registered for \`${addon.name}\`. ` +
// `Please make sure your template precompiler (commonly \`ember-cli-htmlbars\`) is listed in \`dependencies\` ` +
// `(NOT \`devDependencies\`) in \`${addon.name}\`'s \`package.json\`.`);
// });

// it('should throw a useful error if a template compiler is not present -- pods', function() {
// addon.root = path.join(fixturePath, 'with-addon-pod-templates');

// expect(() => {
// addon.compileTemplates();
// }).to.throw(
// `Addon templates were detected, but there are no template compilers registered for \`${addon.name}\`. ` +
// `Please make sure your template precompiler (commonly \`ember-cli-htmlbars\`) is listed in \`dependencies\` ` +
// `(NOT \`devDependencies\`) in \`${addon.name}\`'s \`package.json\`.`
// );
// });

it('should not throw an error if addon/templates is present but empty', function() {
addon.root = path.join(fixturePath, 'with-empty-addon-templates');
Expand Down

0 comments on commit 4670071

Please sign in to comment.