Neutrino Library Preset¶
@neutrinojs/library
is a Neutrino preset that supports creating JS libraries
for Web or Node.js targets.
Features¶
- Zero upfront configuration necessary to start developing and building a JavaScript library
- Modern Babel compilation supporting ES modules, async functions, and dynamic imports
- Compiles to ES5 to support major browsers, publishing to npm, and library interoperability
- Defaults to UMD-based output for consumption in a variety of environments
- Supports automatically-wired sourcemaps
- Tree-shaking to create smaller bundles
- Automatically marks dependencies as external
- Easily extensible to customize your library 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/library
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/library webpack webpack-cli
npm¶
❯ npm install --save-dev neutrino @neutrinojs/library 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:
// Create a simple logger library that only logs when
// debug mode is enabled, and logs to the appropriate level
export default class Logger {
constructor(level, debug = true) {
this.level = level;
this.debug = debug;
}
log(...args) {
if (this.debug) {
console[level](...args);
}
}
}
Now edit your project's package.json
to add commands for building the library:
{
"name": "super-logger",
"scripts": {
"build": "webpack --mode production"
}
}
Then create a .neutrinorc.js
file alongside package.json
, which contains
your Neutrino configuration:
const library = require('@neutrinojs/library');
module.exports = {
use: [
library({
name: 'Logger',
}),
],
};
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();
You can now build your library!
Yarn¶
❯ yarn build
npm¶
❯ npm start
Project Layout¶
@neutrinojs/library
follows the standard
project layout specified by Neutrino.
This means that by default all library 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/library
builds assets to the build
directory by default when
running yarn build
. You should specify a main
property in your package.json
pointing to your primary built main 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
.
{
"name": "super-logger",
"main": "build/index.js",
"files": ["build"]
}
Your built library can now be consumed with ES imports, CJS require, AMD require, or even script tags:
// ES imports
import Logger from 'super-logger';
// CJS require
const Logger = require('super-logger');
// AMD require
require(['super-logger'], (Logger) => {
// ...
});
<!-- script tags -->
<script src="/path/to/super-logger"></script>
<!--
once published to npm, you can even use a script tag
to point to unpkg
-->
<script src="https://unpkg.com/super-logger"></script>
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 Library preset
settings from .neutrinorc.js
by overriding with an options object. The
following shows how you can pass an options object to the Library preset and
override its options, showing the defaults where applicable:
const library = require('@neutrinojs/library');
module.exports = {
use: [
library({
// REQUIRED. Sets the name of the library, along with its
// output filename.
name: '<YOUR_LIBRARY_NAME>',
// Compile the library for use in a specific environment.
// Defaults to 'web', but can be switched to 'node' as well
target: 'web',
// Configure how the library will be exposed. Keeping this set
// to 'umd' ensures compatibility with a large number of module
// systems, but you can override if you want to produce a smaller
// bundle targeted to specific module systems like 'commonjs2' (CJS).
libraryTarget: 'umd',
// Override options passed to webpack-node-externals,
// such as whitelisting dependencies for bundling.
externals: {},
// Disable cleaning the output build directory
clean: false,
// Target specific browsers or Node.js versions with @babel/preset-env
targets: {},
// Add additional Babel plugins, presets, or env options
babel: {
// Override options for @babel/preset-env
presets: [
[
'@babel/preset-env',
{
debug: neutrino.options.debug,
useBuiltIns: 'usage',
},
],
],
},
}),
],
};
Example: Override the library to output a commonjs2 module:
const library = require('@neutrinojs/library');
module.exports = {
use: [
library({
name: 'Logger',
target: 'node',
libraryTarget: 'commonjs2',
}),
],
};
Targets¶
Using the targets
option, you can target specific browsers or Node.js versions
to compile to with @babel/preset-env
. By default, this preset does not set any
targets and will compile to an ES5 baseline.
Example: Override the library Babel compilation target to Node.js v6:
const library = require('@neutrinojs/library');
module.exports = {
use: [
library({
name: 'Logger',
target: 'node',
targets: {
node: '6.0',
},
}),
],
};
Setting to false
will override Neutrino's default targets and allow
@babel/preset-env
to read targets from a
.browserslistrc
file.
const library = require('@neutrinojs/library');
module.exports = {
use: [
library({
name: 'Logger',
target: 'node',
targets: false,
}),
],
};
When using a .browserslistrc
file, be aware that file changes may not
invalidate cache as expected: https://github.com/babel/babel-loader/issues/690
See @babel/preset-env
for all other available settings.
Customizing¶
To override the build configuration, start with the documentation on
customization. @neutrinojs/library
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 library, 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 library. 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.
External dependencies¶
This preset automatically marks all dependencies as external to your library,
meaning that any dependencies you import will not be bundled with your
library. This helps prevent your library from bloating, but means users of your
library will be installing or using your dependencies as defined in
package.json. You can override this in the library preset options by passing
further options to the externals
property. This accepts an options object
format defined by
webpack-node-externals,
to which you can provide a whitelist
value.
The whitelist will override which dependencies are bundled in your library. Any dependency not matched by this whitelist is considered a peer of your library, and will not be bundled.
Example: The following example library redux-example
has the following
package.json, marking redux
and mitt
as dependencies, but only lists mitt
in whitelist. This bundles mitt
along with the library, so the library
consumer does not need to explicitly import/require/script it prior. The redux
dependency is not in the whitelist, so it will not be bundled, and is considered
a peer of redux-example
.
{
"name": "redux-example",
"version": "1.0.0",
"main": "build/reduxExample.js",
"scripts": {
"build": "webpack --mode production"
},
"dependencies": {
"mitt": "*",
"redux": "*"
},
"devDependencies": {
"neutrino": "*",
"@neutrinojs/library": "*"
}
}
const library = require('@neutrinojs/library');
module.exports = {
use: [
library({
name: 'reduxExample',
externals: {
whitelist: ['mitt'],
},
}),
],
};
// ES imports
import { createStore } from 'redux';
import reduxExample from 'redux-example';
// CJS require
const { createStore } = require('redux');
const reduxExample = require('redux-example');
// AMD require
require(['redux', 'redux-example'], ({ createStore }, reduxExample) => {
// ...
});
<!-- script tags -->
<script src="/path/to/redux"></script>
<script src="/path/to/redux-example"></script>
<script>
const { createStore } = window.redux;
window.reduxExample; // ...
</script>
<!--
once published to npm, you can even use a script tag
to point to unpkg
-->
<script src="https://unpkg.com/redux"></script>
<script src="https://unpkg.com/redux-example"></script>
<script>
const { createStore } = window.redux;
window.reduxExample; // ...
</script>
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 library = require('@neutrinojs/library');
module.exports = {
use: [
library({ name: 'reduxExample' }),
(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,
},
},
]);
}
},
],
};
Generating multiple builds¶
A library can be built multiple times from a webpack.config.js
file in order
to generate multiple library outputs when building. This is done by exporting an
array of Neutrino outputs rather than a single output.
// .neutrinorc.js
const library = require('@neutrinojs/library');
module.exports = {
use: [
library({
name: 'Logger',
clean: false,
}),
],
};
// webpack.config.js
const neutrino = require('neutrino');
const config = neutrino().webpack();
module.exports = [config, { ...config, libraryTarget: 'commonjs2' }];
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 main 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' |
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 library = require('@neutrinojs/library');
module.exports = {
use: [
library({
/* ... */
}),
(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.