Skip to content

Commit

Permalink
feature #401 Allow to configure the JS exclude rules through configur…
Browse files Browse the repository at this point in the history
…eBabel (Lyrkan)

This PR was squashed before being merged into the master branch (closes #401).

Discussion
----------

Allow to configure the JS exclude rules through configureBabel

This PR closes #342 by allowing to configure the js/jsx loaders' exclude rule by using `configureBabel()`.

For instance:

```js
Encore.configureBabel(
    () => {},
    { exclude: /foo/ }
);
```

Note that it doesn't change the default behavior that excludes `node_modules` and `bower_components`.

**Edit:**

Also adds an `include_node_modules` option to use the default `exclude` rule but only include some Node modules (can't be used if the `exclude` option is also set):

```js
Encore.configureBabel(
    () => {},
    { include_node_modules: ['foo', 'bar', 'baz'] }
);
```

Commits
-------

a07238c Add an example of calling configureBabel with the "include_node_modules" option
714a732 Add "include_node_modules" option to configureBabel
6c8f073 Allow to configure the JS exclude rules through configureBabel
  • Loading branch information
weaverryan committed Oct 26, 2018
2 parents 94e65e7 + a07238c commit b171eb4
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 4 deletions.
27 changes: 25 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -716,13 +716,36 @@ class Encore {
*
* Encore.configureBabel(function(babelConfig) {
* // change the babelConfig
* }, {
* // set optional Encore-specific options, for instance:
*
* // change the rule that determines which files
* // won't be processed by Babel
* exclude: /bower_components/
*
* // ...or keep the default rule but only allow
* // *some* Node modules to be processed by Babel
* include_node_modules: ['foundation-sites']
* });
*
* Supported options:
* * {Condition} exclude (default=/(node_modules|bower_components)/)
* A Webpack Condition passed to the JS/JSX rule that
* determines which files and folders should not be
* processed by Babel (https://webpack.js.org/configuration/module/#condition).
* Cannot be used if the "include_node_modules" option is
* also set.
* * {string[]} include_node_modules
* If set that option will include the given Node modules to
* the files that are processed by Babel. Cannot be used if
* the "exclude" option is also set.
*
* @param {function} callback
* @param {object} encoreOptions
* @returns {Encore}
*/
configureBabel(callback) {
webpackConfig.configureBabel(callback);
configureBabel(callback, encoreOptions = {}) {
webpackConfig.configureBabel(callback, encoreOptions);

return this;
}
Expand Down
42 changes: 41 additions & 1 deletion lib/WebpackConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ class WebpackConfig {
images: false,
fonts: false
};
this.babelOptions = {
exclude: /(node_modules|bower_components)/
};

// Features/Loaders options callbacks
this.postCssLoaderOptionsCallback = () => {};
Expand Down Expand Up @@ -302,7 +305,7 @@ class WebpackConfig {
this.useSourceMaps = enabled;
}

configureBabel(callback) {
configureBabel(callback, options = {}) {
if (typeof callback !== 'function') {
throw new Error('Argument 1 to configureBabel() must be a callback function.');
}
Expand All @@ -312,6 +315,43 @@ class WebpackConfig {
}

this.babelConfigurationCallback = callback;

for (const optionKey of Object.keys(options)) {
if (optionKey === 'include_node_modules') {
if (Object.keys(options).includes('exclude')) {
throw new Error('"include_node_modules" and "exclude" options can\'t be used together when calling configureBabel().');
}

if (!Array.isArray(options[optionKey])) {
throw new Error('Option "include_node_modules" passed to configureBabel() must be an Array.');
}

this.babelOptions['exclude'] = (filePath) => {
// Don't exclude modules outside of node_modules/bower_components
if (!/(node_modules|bower_components)/.test(filePath)) {
return false;
}

// Don't exclude whitelisted Node modules
const whitelistedModules = options[optionKey].map(
module => path.join('node_modules', module) + path.sep
);

for (const modulePath of whitelistedModules) {
if (filePath.includes(modulePath)) {
return false;
}
}

// Exclude other modules
return true;
};
} else if (!(optionKey in this.babelOptions)) {
throw new Error(`Invalid option "${optionKey}" passed to configureBabel(). Valid keys are ${Object.keys(this.babelOptions).join(', ')}`);
} else {
this.babelOptions[optionKey] = options[optionKey];
}
}
}

configureCssLoader(callback) {
Expand Down
2 changes: 1 addition & 1 deletion lib/config-generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ class ConfigGenerator {
{
// match .js and .jsx
test: /\.jsx?$/,
exclude: /(node_modules|bower_components)/,
exclude: this.webpackConfig.babelOptions.exclude,
use: babelLoaderUtil.getLoaders(this.webpackConfig)
},
{
Expand Down
63 changes: 63 additions & 0 deletions test/WebpackConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,45 @@ describe('WebpackConfig object', () => {
const testCallback = () => {};
config.configureBabel(testCallback);
expect(config.babelConfigurationCallback).to.equal(testCallback);
expect(String(config.babelOptions.exclude)).to.equal(String(/(node_modules|bower_components)/));
});

it('Calling with "exclude" option', () => {
const config = createConfig();
config.configureBabel(() => {}, { exclude: 'foo' });

expect(config.babelOptions.exclude).to.equal('foo');
});

it('Calling with "include_node_modules" option', () => {
const config = createConfig();
config.configureBabel(() => {}, { include_node_modules: ['foo', 'bar'] });

expect(config.babelOptions.exclude).to.be.a('Function');

const includedPaths = [
path.join('test', 'lib', 'index.js'),
path.join('test', 'node_modules', 'foo', 'index.js'),
path.join('test', 'node_modules', 'foo', 'lib', 'index.js'),
path.join('test', 'node_modules', 'bar', 'lib', 'index.js'),
path.join('test', 'node_modules', 'baz', 'node_modules', 'foo', 'index.js'),
];

const excludedPaths = [
path.join('test', 'bower_components', 'foo', 'index.js'),
path.join('test', 'bower_components', 'bar', 'index.js'),
path.join('test', 'bower_components', 'baz', 'index.js'),
path.join('test', 'node_modules', 'baz', 'lib', 'index.js'),
path.join('test', 'node_modules', 'baz', 'lib', 'foo', 'index.js')
];

for (const filePath of includedPaths) {
expect(config.babelOptions.exclude(filePath)).to.equal(false);
}

for (const filePath of excludedPaths) {
expect(config.babelOptions.exclude(filePath)).to.equal(true);
}
});

it('Calling with non-callback throws an error', () => {
Expand All @@ -489,6 +528,30 @@ describe('WebpackConfig object', () => {
config.configureBabel(() => {});
}).to.throw('configureBabel() cannot be called because your app already has Babel configuration');
});

it('Pass invalid config', () => {
const config = createConfig();

expect(() => {
config.configureBabel(() => {}, { fake_option: 'foo' });
}).to.throw('Invalid option "fake_option" passed to configureBabel()');
});

it('Calling with both "include_node_modules" and "exclude" options', () => {
const config = createConfig();

expect(() => {
config.configureBabel(() => {}, { exclude: 'foo', include_node_modules: ['bar', 'baz'] });
}).to.throw('can\'t be used together');
});

it('Calling with an invalid "include_node_modules" option value', () => {
const config = createConfig();

expect(() => {
config.configureBabel(() => {}, { include_node_modules: 'foo' });
}).to.throw('must be an Array');
});
});

describe('configureCssLoader', () => {
Expand Down
29 changes: 29 additions & 0 deletions test/config-generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -826,6 +826,35 @@ describe('The config-generator function', () => {
});
});

describe('Test configureBabel()', () => {
it('without configureBabel()', () => {
const config = createConfig();
config.outputPath = '/tmp/output/public-path';
config.publicPath = '/public-path';
config.addEntry('main', './main');

const actualConfig = configGenerator(config);

const jsRule = findRule(/\.jsx?$/, actualConfig.module.rules);
expect(String(jsRule.exclude)).to.equal(String(/(node_modules|bower_components)/));
});

it('with configureBabel() and a different exclude rule', () => {
const config = createConfig();
config.outputPath = '/tmp/output/public-path';
config.publicPath = '/public-path';
config.addEntry('main', './main');
config.configureBabel(() => {}, {
exclude: /foo/
});

const actualConfig = configGenerator(config);

const jsRule = findRule(/\.jsx?$/, actualConfig.module.rules);
expect(String(jsRule.exclude)).to.equal(String(/foo/));
});
});

describe('Test shouldSplitEntryChunks', () => {
it('Not production', () => {
const config = createConfig();
Expand Down

0 comments on commit b171eb4

Please sign in to comment.