Neutrino Node.js Preset
@neutrinojs/node
is a Neutrino preset that supports building Node.js
applications.
Features
- Zero upfront configuration necessary to start developing and building a Node.js project
- Modern Babel compilation supporting ES modules, async functions, and dynamic imports
- By default targets the version of Node.js used to run webpack
- Supports automatically-wired sourcemaps
- Tree-shaking to create smaller bundles
- Hot Module Replacement with source-watching during development
- Chunking of external dependencies apart from application code
- Easily extensible to customize your project as needed
Important! If you need polyfills in your code, consider including core-js
in
your package.json
. This is will configure @babel/present-env
to
automatically include polyfills based on usage. More details
here.
Requirements
- Node.js 10+
- Yarn v1.2.1+, or npm v5.4+
- Neutrino 9
- webpack 4
- webpack-cli 3
Quickstart
The fastest way to get started is by using the create-project
scaffolding
tool. Don’t want to use the CLI helper? No worries, we have you covered with the
manual installation.
create-project
Run the following command to start the process. Substitute <directory-name>
with the directory name you wish to create for this project.
Yarn
❯ yarn create @neutrinojs/project <directory-name>
Note: The create
command is a shorthand that helps you do two things at once.
See the Yarn create docs for
more details.
npm/npx
npx
comes pre-installed with npm
. If you’re
running an older version of npm
, then npm install -g npm
to update to the
latest version.
❯ npx @neutrinojs/create-project <directory-name>
The CLI helper will prompt for the project to scaffold, and will offer to set up a test runner as well as linting to your project. Refer to the Create new project section for details on all available options.
Manual Installation
@neutrinojs/node
can be installed via the Yarn or npm clients. Inside your
project, make sure that the dependencies below are installed as development
dependencies.
Yarn
❯ yarn add --dev neutrino @neutrinojs/node webpack webpack-cli
npm
❯ npm install --save-dev neutrino @neutrinojs/node webpack webpack-cli
If you want to have automatically wired sourcemaps added to your project, add
source-map-support
:
Yarn
❯ yarn add source-map-support
npm
❯ npm install --save source-map-support
After that, add a new directory named src
in the root of the project, with a
single JS file named index.js
in it.
❯ mkdir src && touch src/index.js
Edit your src/index.js
file with the following:
import { createServer } from 'http';
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const port = process.env.PORT || 3000;
createServer(async (req, res) => {
await delay(500);
console.log('Request!');
res.end('hi!');
}).listen(port, () => console.log(`Server running on port ${port}`));
Now edit your project's package.json
to add commands for starting and building
the application:
{
"scripts": {
"start": "webpack --watch --mode development",
"build": "webpack --mode production"
}
}
Then create a .neutrinorc.js
file alongside package.json
, which contains
your Neutrino configuration:
const node = require('@neutrinojs/node');
module.exports = {
use: [node()],
};
And create a webpack.config.js
file, that uses the Neutrino API to access the
generated webpack config:
const neutrino = require('neutrino');
module.exports = neutrino().webpack();
Start the app, then either open a browser to the address in the console, or use curl from another terminal window:
Yarn
❯ yarn start
Server running on port 3000
❯ curl http://localhost:3000
hi!
npm
❯ npm start
Server running on port 3000
❯ curl http://localhost:3000
hi!
Project Layout
@neutrinojs/node
follows the standard
project layout specified by Neutrino.
This means that by default all project source code should live in a directory
named src
in the root of the project. This includes JavaScript files that
would be available to your compiled project.
Building
@neutrinojs/node
builds assets to the build
directory by default when
running yarn build
. You can either serve or deploy the contents of this
build
directory as a Node.js module, server, or tool. For Node.js this usually
means adding a main
property to package.json pointing to the primary main
built entry point. Also when publishing your project to npm, consider excluding
your src
directory by using the files
property to whitelist build
, or via
.npmignore
to blacklist src
.
{
"main": "build/index.js",
"files": ["build"]
}
Note: While this preset works well for many types of Node.js applications, it's important to make the distinction between applications and libraries. This preset will not work optimally out of the box for creating distributable libraries, and will take a little extra customization to make them suitable for that purpose.
Hot Module Replacement
While @neutrinojs/node
supports Hot Module Replacement for your app, it does
require some application-specific changes in order to operate. Your application
should define split points for which to accept modules to reload using
module.hot
:
For example:
import { createServer } from 'http';
import app from './app';
if (module.hot) {
module.hot.accept('./app');
}
createServer((req, res) => {
res.end(app('example'));
}).listen(/* */);
Or for all paths:
import { createServer } from 'http';
import app from './app';
if (module.hot) {
module.hot.accept();
}
createServer((req, res) => {
res.end(app('example'));
}).listen(/* */);
Using dynamic imports with import()
will automatically create split points and
hot replace those modules upon modification during development.
Debugging
You can start the Node.js server in inspect
mode to debug the process by
setting neutrino.options.debug
to true
.
Preset options
You can provide custom options and have them merged with this preset's default
options to easily affect how this preset builds. You can modify Node.js preset
settings from .neutrinorc.js
by overriding with an options object. The
following shows how you can pass an options object to the Node.js preset and
override its options, showing the defaults:
const node = require('@neutrinojs/node');
module.exports = {
use: [
node({
// Enables Hot Module Replacement. Set to false to disable
hot: true,
// Targets the version of Node.js used to run webpack.
targets: {
node: 'current',
},
// Disable cleaning the output build directory
clean: false,
// Add additional Babel plugins, presets, or env options
babel: {
// Override options for @babel/preset-env, showing defaults:
presets: [
[
'@babel/preset-env',
{
// Targets the version of Node.js used to run webpack.
targets: { node: 'current' },
useBuiltIns: 'usage',
},
],
],
},
}),
],
};
Example: Override the Node.js Babel compilation target to Node.js v6:
const node = require('@neutrinojs/node');
module.exports = {
use: [
node({
// Target specific versions via @babel/preset-env
targets: {
node: '6.0',
},
}),
],
};
Customizing
To override the build configuration, start with the documentation on
customization. @neutrinojs/node
creates some conventions to make overriding the configuration easier once you
are ready to make changes.
By default Neutrino, and therefore this preset, creates a single main
index
entry point to your application, and this maps to the index.*
file in
the src
directory. This means that this preset is optimized toward a single
main entry to your application. Code not imported in the hierarchy of the
index
entry will not be output to the bundle. To overcome this you must either
define more mains via
options.mains
, import
the code path somewhere along the index
hierarchy, or define multiple
configurations in your .neutrinorc.js
.
If the need arises, you can also compile node_modules
by referring to the
relevant
compile-loader
documentation.
Vendoring
This preset automatically vendors all external dependencies into a separate chunk based on their inclusion in your package.json. No extra work is required to make this work.
Source minification
By default script sources are minified in production only, using webpack's
default of
terser-webpack-plugin.
To customise the options passed to TerserPlugin
or even use a different
minifier, override optimization.minimizer
.
Example: Adjust the terser
minification settings:
const node = require('@neutrinojs/node');
module.exports = {
use: [
node(),
(neutrino) => {
// Whilst the minimizer is only used when the separate `minimize` option is true
// (ie in production), the conditional avoids the expensive require() in development.
if (process.env.NODE_ENV === 'production') {
neutrino.config.optimization
.minimizer('terser')
.use(require.resolve('terser-webpack-plugin'), [
{
// Default options used by webpack:
// https://github.com/webpack/webpack/blob/v4.26.0/lib/WebpackOptionsDefaulter.js#L308-L315
cache: true,
parallel: true,
sourceMap:
neutrino.config.devtool &&
/source-?map/.test(neutrino.config.devtool),
// Pass custom options here.
// https://github.com/webpack-contrib/terser-webpack-plugin#terseroptions
// https://github.com/terser-js/terser#minify-options
terserOptions: {
// eg disable mangling of names
mangle: false,
},
},
]);
}
},
],
};
Rules
The following is a list of rules and their identifiers which can be overridden:
Name | Description | NODE_ENV |
---|---|---|
compile |
Compiles JS files from the src directory using Babel. Contains a single loader named babel |
all |
Plugins
The following is a list of plugins and their identifiers which can be overridden:
Note: Some plugins are only available in certain environments. To override them, they should be modified conditionally.
Name | Description | NODE_ENV |
---|---|---|
banner |
Injects source-map-support into the mains (entry points) of your application if detected in dependencies or devDependencies of your package.json. |
Only when source-map-support is installed |
clean |
Clean or remove the build directory prior to building. From @neutrinojs/clean . |
'production' |
start-server |
Start a Node.js for the first configured main entry point. | 'development' |
hot |
Enables Hot Module Replacement. | 'development' |
Override configuration
By following the customization guide
and knowing the rule, loader, and plugin IDs above, you can override and augment
the build by by providing a function to your .neutrinorc.js
use array. You can
also make these changes from the Neutrino API in custom middleware.
Example: Allow importing modules with a .esm
extension.
const node = require('@neutrinojs/node');
module.exports = {
use: [
node(),
(neutrino) => {
neutrino.config.resolve.extensions.add('.esm');
},
],
};
Contributing
This preset is part of the neutrino repository, a monorepo containing all resources for developing Neutrino and its core presets and middleware. Follow the contributing guide for details.