Neutrino Customization¶
No two JavaScript projects are ever the same, and as such there may be times when you will need to make modifications to the way your Neutrino presets are building your project. Neutrino provides a mechanism to augment presets and middleware in the context of a project without resorting to creating and publishing an entirely independent preset.
Your .neutrinorc.js
file is a JavaScript module which will be required by Neutrino using Node.js. Any code written in
this file should be usable by the version of Node.js you have running on your system when running Neutrino. The
.neutrinorc.js
file should export an object or function depending on which format you opt to use.
module.exports = {
/* make customizations */
};
module.exports = (neutrino) => {
/* make customizations */
};
Overriding Neutrino options¶
Neutrino has a number of useful options for customizing its behavior, and these can be overridden by using an
object at the options
property:
options.root
¶
Set the base directory which Neutrino middleware and presets operate on. Typically this is the project directory where
the package.json would be located. If the option is not set, Neutrino defaults it to process.cwd()
. If a relative
path is specified, it will be resolved relative to process.cwd()
; absolute paths will be used as-is.
It's recommended to always set this value, to ensure that tools such as ESLint work when run from a subdirectory
of the repository. If your .neutrinorc.js
is in the root of the repository, use the value __dirname
to achieve this.
module.exports = {
options: {
// Override to root of project
root: __dirname,
// Override to relative directory, resolves to process.cwd() + website
root: 'website',
// Override to absolute directory
root: '/code/website'
}
};
options.source
¶
Set the directory which contains the application source code. If the option is not set, Neutrino defaults it to src
.
If a relative path is specified, it will be resolved relative to options.root
; absolute paths will be used as-is.
module.exports = {
options: {
// Override to relative directory, resolves to options.root + lib
source: 'lib',
// Override to absolute directory
source: '/code/website/lib'
}
};
options.output
¶
Set the directory which will be the output of built assets. If the option is not set, Neutrino defaults it to build
.
If a relative path is specified, it will be resolved relative to options.root
; absolute paths will be used as-is.
module.exports = {
options: {
// Override to relative directory, resolves to options.root + dist
output: 'dist',
// Override to absolute directory
output: '/code/website/dist'
}
};
options.tests
¶
Set the directory that contains test files. If the option is not set, Neutrino defaults it to test
.
If a relative path is specified, it will be resolved relative to options.root
; absolute paths will be used as-is.
module.exports = {
options: {
// Override to relative directory, resolves to options.root + testing
tests: 'testing',
// Override to absolute directory
tests: '/code/website/testing'
}
};
options.mains
¶
Set the main entry points for the application. If the option is not set, Neutrino defaults it to:
{
index: 'index'
}
Notice the entry point has no extension; the extension is resolved by webpack. If relative paths are specified,
they will be computed and resolved relative to options.source
; absolute paths will be used as-is.
Multiple entry points and any page-specific configuration (if supported by the preset) can be specified like so:
module.exports = {
options: {
mains: {
// Relative path, so resolves to options.source + home.*
index: 'home',
// Absolute path, used as-is.
login: '/code/website/src/login.js',
// Long form that allows passing page-specific configuration
// (such as html-webpack-plugin options in the case of @neutrinojs/web).
admin: {
entry: 'admin',
// any page-specific options here (see preset docs)
// ...
}
}
}
};
Mutating neutrino.options
¶
While it is possible to mutate neutrino.options
directly, this should be avoided if possible.
Instead, it is recommended to pass an options
object via the object-based export to
ensure proper normalization.
// Bad: Overriding `neutrino.options` properties.
// Paths will not be relative to `neutrino.options.root` as expected.
module.exports = neutrino => {
Object.assign(neutrino.options, {
source: 'lib',
output: 'dist'
});
};
// Good: Setting `neutrino.options.*` properties directly.
module.exports = neutrino => {
neutrino.options.source = 'lib';
neutrino.options.output = 'dist';
};
// Good: Use object export format for merging.
module.exports = {
options: {
source: 'lib',
output: 'dist'
},
use: [
/* ... */
]
};
Using middleware¶
By specifying a use
array in your .neutrinorc.js
, you can inform Neutrino to load middleware when it
runs. Each item in this use
array can be a middleware function.
In its simplest form, you can require middleware packages and pass them to Neutrino:
const airbnb = require('@neutrinojs/airbnb');
const react = require('@neutrinojs/react');
const jest = require('@neutrinojs/jest');
module.exports = {
use: [
airbnb(),
react(),
jest()
]
};
If your middleware module supports its own options via a closure, pass them into the middleware factory:
const airbnb = require('@neutrinojs/airbnb');
const react = require('@neutrinojs/react');
const jest = require('@neutrinojs/jest');
module.exports = {
use: [
airbnb({
eslint: {
rules: {
semi: 'off'
}
}
}),
react({
html: { title: 'Epic React App' }
}),
jest()
]
};
If you need to make more advanced configuration changes, you can even directly pass a function as middleware to use
and have access to the Neutrino API:
const airbnb = require('@neutrinojs/airbnb');
const react = require('@neutrinojs/react');
const jest = require('@neutrinojs/jest');
module.exports = {
use: [
airbnb(),
react(),
jest(),
(neutrino) => {
neutrino.config.module
.rule('style')
.use('css')
.options({ modules: true });
}
]
};
Environment-specific overrides¶
Sometimes you can only make certain configuration changes in certain Node.js environments, or you may choose to
selectively make changes based on the values of any arbitrary environment variable. This can be achieved by
conditionally applying middleware in .neutrinorc.js
.
For example, if you wanted to include additional middleware when NODE_ENV
is test
:
const jest = require('@neutrinojs/jest');
module.exports = {
use: [
process.env.NODE_ENV === 'test' ? jest() : false,
]
};
Example: Turn on CSS modules when the environment variable CSS_MODULES=enable
:
module.exports = {
use: [
(neutrino) => {
// Turn on CSS modules when the environment variable CSS_MODULES=enable
if (process.env.CSS_MODULES === 'enable') {
neutrino.config.module
.rule('style')
.use('css')
.options({ modules: true });
}
}
]
};
Advanced configuration changes¶
Making deep or complex changes to Neutrino build configuration beyond what middleware options afford you can be done
using a middleware function. If you wish, your entire .neutrinorc.js
file can be a middleware function, but
typically this function can be inlined directly as an additional item in the use
array.
If you're familiar with middleware from the Express/connect world, this works similarly. When using Express middleware, you provide a function to Express which receives arguments to modify a request or response along its lifecycle. There can be a number of middleware functions that Express can load, each one potentially modifying a request or response in succession.
When you add a middleware function to use
, this is typically used to override Neutrino's configuration, and you can
add as many functions as you wish in succession. Every preset or middleware that Neutrino has loaded follows this same
middleware successive pipeline.
The Neutrino API instance provided to your function has a config
property that is an instance of
webpack-chain. We won't go in-depth of all the configuration
possibilities here, but encourage you to check out the documentation for webpack-chain for instructions on your
particular use case. Just know that you can use webpack-chain to modify any part of the underlying webpack configuration
using its API.
This neutrino.config
is an accumulation of all configuration up to this moment. All Neutrino middleware and presets
interact with and make changes through this config, which is all available to you. For example, if you are using the
presets @neutrinojs/react
and @neutrinojs/karma
, any config set can be extended, manipulated, or removed.
Example: Neutrino's React preset adds .jsx
as a module extension. Let's remove it.
const react = require('@neutrinojs/react');
const karma = require('@neutrinojs/karma');
module.exports = {
use: [
react(),
karma(),
(neutrino) => {
neutrino.config.resolve.extensions.delete('.jsx');
}
]
};
Example: Neutrino's Node.js preset has performance hints disabled. Let's re-enable them.
const node = require('@neutrinojs/node');
module.exports = {
use: [
node(),
(neutrino) => {
neutrino.config.performance.hints('error');
}
]
};
Remember, middleware can also have their own custom options for making some changes easier without having to resort to interacting with the Neutrino API; see your respective middleware for details. See the documentation on the configuration API using webpack-chain for all ways you can modify a config instance to solve your use cases.
Conditional configuration¶
Some plugins and rules are only available in certain environments. For example, the Web preset only exposes an optimize-css
plugin during production, leading to issues when trying to modify its settings, but throws an exception during
development.
Example: Remove all arguments to the optimize-css
plugin when using the Web preset.
config.when(process.env.NODE_ENV === 'production', config => {
config.plugin('optimize-css').tap(args => []);
});