Skip to main content

Javascript configuration

In the configuration file you will want to set up how the Javscript files will be built. You can also configure eslint processing.

Processing Javscript files

There are three ways that Javascript files can be processed.

  1. As a bundle with the javascript.bundles configuration array. This will join files together and output them in a single file in the build folder as a minified file. It does not support importing. It simply concats the files together in the order that they are configured.
  2. As a single file that is minified and then output in the build folder. The only processing is to minimize the file. This is ideal for simple files.
  3. Build the Javascript files with esbuild. This option is ideal for more complex Javascript. It supports importing during the build process. The output is a minimized file in the IIFE format.

Javascript bundles

You can concat Javascript files together and minify the output before saving to the build folder. The javascript.bundles configuration array holds that inforamation.

The javascript.bundles array holds an array of objects that specify each bundle.

Each bundle object can have the following values.

  • build (Required) (string) The name of the bundled file that will be output in the Javascript build directory (javascript.build) and optionally a directory path. if you're ok with the file going in the javascript.build path then this can just be the file name.
  • nodeModules (string|array) A string containing the path to a single file or glob to include in the bundle from the node_modules folder. Or, an array of files or globs to include in the bundle from the node_modules folder. The path should start with the package name and not with the node_modules folder.
  • src (string|array) A string containing the path to a single file or glob in the Javascript src folder (javascript.src) to include in the bundle. Or, an array of files or globs in the Javascript source folder to include in the bundle. The path should start within the Javascript source folder and not include the source folder name.

In addition to build, you must at least set nodeModules or src. You can set both. Here are some examples:

{
javascript: {
bundles: [
{
build: 'name-of-file.js',
src: 'folder/**/*.js',
}
]
}
}
{
javascript: {
bundles: [
{
build: 'name-of-file.js',
src: [
'file1.js',
'folder/file2.js',
'another-file.js',
'glob-folder/*.js'
]
}
]
}
}
{
javascript: {
bundles: [
{
build: 'path/to/name-of-file.js',
nodeModules: [
'packageName/path/to/file.js',
'anotherPackage/path/**/*.js'
],
src: [
'some-file.js',
'folder/*.js'
]
}
]
}
}
{
javascript: {
bundles: [
{
build: 'name-of-file.js',
nodeModules: 'anotherPackage/path/**/*.js',
src: 'some-file.js',
}
]
}
}

Single Javascript files

If you don't need to bundle a file with another set of files then you can use the javascript.files configuration array to specify files to minify and copy to the build directory.

The javascript.files array is an array of file paths where the file path starts in the Javascript source directory (javascript.src).

{
javascript: {
files: [
'filename.js',
'subFolder/my-file.js'
]
}
}

The files are minified and output in the build directory in the Javascript build directory (javascript.build) with the same file name and path.

Building Javascript files with esbuild

If your Javascript is more complex and you want to utilize importing files then this is the best option.

You can also bundle the esbuild output with another Javascript file. This is useful if you have a library that you want to include on all pages but you don't want to do another network request from the browser to get it.

There are two ways to build Javascript files with esbuild.

  1. Use ES modules
  2. Use CommonJS

The determination for which method is used depeds on the file extension, how you import files, and how you export objects from a file.

ES Modules build method

This is our recommended option because it's similar to how modern development is done and it allows you to use web workers.

The ES Modules build method requires the following:

  • Your files must have the .js file extension.
  • You must use import to import files.
  • You must use export to export objects from a file.

If the entry file exports a variable or an object then that value can be accessed using the globalName value in the esbuild configuration and the name of the variable/object. For example, if the globalName is set to products and your entry file exports an object called productsController and that object has a method called init, then you can access that method in your inline Javascript depending on the way that you export.

// Entry file
// Using export default
const productsController = {
init() {
console.log('init');
}
}
export default productsController;

// Using the exported function in some other file
<script>
products.default.init();
</script>
// Entry file
// Using export
const productsController = {
init() {
console.log('init');
}
}
export {productsController};

// Using the exported function in some other file
<script>
products.productsController.init();
</script>

The ES Modules is the only way to use web workers in your project.

CommonJS build method

The CommonJS method requires the following:

  • Your files must have the .cjs file extension.
  • You must use require() to import files.
  • You must use module.exports to export objects from a file.

If the entry file exports a variable or object then that value can be accessed using the globalName value in the esbuild configuration. For example, if the globalName is set to products and your entry file exports an object called productsController and that object has a method called init, then you can access that method in your inline Javascript like this:

<script>
products.init();
</script>

If you don't set the globalName value then the exported object will not be available in the global scope.

There are some cons to using the CommonJS method.

  1. The file name extension is .cjs, which may confuse some of your team.
  2. You can't use web workers with the CommonJS method. The web worker file name must end in .worker.js and be imported with import. Since import doesn't work with CommonJS, web workers can't be used.

Configure the entry point files

You configure an entry file for your Javascript and in that file you can import other files. The other files can also have their own imports if you want.

You set the entry points with the javascript.entryPoints configuration array. There are two ways to set the entry points.

Set a simple array of file names/paths within the Javascript source directory (javascript.src). The files will be built with the same name as the entrypoint file in the Javascript build directory (javascript.build).

{
javascript: {
entryPoints: [
'filename.js',
'path/filename2.js'
]
}
}

Set an array of objects that specify the in file (i.e. the entry point file) and the name/path of the out file. This is useful if you want to change the name of the output file, or you need to output the file in a subdirectory within the Javascript build directory (javascript.build).

The out value should not contain the file extension. .js is automatically added to the out value by esbuild.

{
javascript: {
entryPoints: [
{ in: 'file.js', out: 'other-name' },
{ in: 'path/filename2.js', out: 'some-path/file-out'}
]
}
}

You can combine both approaches.

{
javascript: {
entryPoints: [
{ in: 'file.js', out: 'other-name' },
{ in: 'path/filename2.js', out: 'some-path/file-out'},
'my-file.js'
]
}
}

You can leave out the out value. If you do then the output file name will match the in file name and it will be built in the root of the Javascript build directory (javascript.build).

{
javascript: {
entryPoints: [
{ in: 'file.js' },
]
}
}

Configuring esbuild

The default esbuild configuration that can be overridden is:

{
minify: true,
}

We don't recommend that you override the default configuration, but you technically can. You could also set other esconfig configuration. Your configuration object will be merged with the default configuration.

Some reasons to add your own esbuild configuration include:

  • You need to expose a global variable to other Javascript. In this case you'd want to set the globalName parameter. This option only matters when the format setting is iife (which stands for immediately-invoked function expression as is the default format). It sets the name of the global variable which is used to store the exports from the entry point.
  • You need to include some legal comments with the output file. Use the legalComments parameter.

There are two ways to set the esbuild configuration.

Set the configuration in the javascript.esConfig value:

{
javascript: {
entryPoints: [
'my-file.js'
],
esConfig: {
globalName: 'myObject',
legalComments: 'inline'
}
}
}

Set the configuration in the javascript.entryPoints array. You would do this by setting the entry point file as an object. While it's recommended to set the out value, you don't have to. Set the configuration with the config value in the file object.

{
javascript: {
entryPoints: [
{ in: 'file.js', out: 'outfile', config: { globalName: 'fileObject' } },
{ in: 'path/filename2.js', out: 'some-path/file-out'},
{ in: 'other-file.js', config: { globalName: 'myFile' } },
]
}
}

If no configuration is set in the file object and the javascript.esConfig option is not set, then only the default esbuild configuration will be used.

If the javascript.esConfig option is set and the config value is set in the file option, then they will both be merged into the esbuild configuration. First the javascript.esConfig value and then the config value for the individual entry point.

You can use any of the configuration parameters for esbuild.

Setting global variable

The IIFE format wraps the code in a self executing function, thus isolating any variables. If you need to expose a variable to other Javascript (like perhaps some inline Javascript on a page) then you need to set the globalName parameter.

How you use that variable to access your code depends on if you're using the ES Modules method or the CommonJS method.

ES Modules global variable

Use export to export.

const thingToExport = {};
export default thingToExport;

If you want to export multiple variables you can do it like this:

const variable1 = {};
const variable2 = {};

export default {
variable1,
variable2
}

If the entry file exports a variable or an object then that value can be accessed using the globalName value in the esbuild configuration and the name of the variable/object. For example, if the globalName is set to products and your entry file exports an object called productsController and that object has a method called init, then you can access that method in your inline Javascript depending on the way that you export.

// Entry file
// Using export default
const productsController = {
init() {
console.log('init');
}
}
export default productsController;

// Using the exported function in some other file
<script>
products.default.init();
</script>
// Entry file
// Using export
const productsController = {
init() {
console.log('init');
}
}
export {productsController};

// Using the exported function in some other file
<script>
products.productsController.init();
</script>

We recommend exporting the object as setting it to the helper name. It seems cleaner and avoids possible duplication of the global/object name.

// Entry file
// Using export
const productsController = {
init() {
console.log('init');
}
}
//// eslint-disable-next-line import/prefer-default-export -- See http://aptuitiv.github.io/website-build-tools/configuration/javascript#es-modules-build-method for exporting recommendations.
export {productsController as helper};

// Using the exported function in some other file
<script>
products.helper.init();
</script>

CommonJS global variable

Use module.exports to export.

const thingToExport = {};

module.exports = thingToExport;

If you want to export multiple variables you can do it like this:

module.exports = {
variable1,
variable2
}

If the entry file exports a variable or object then that value can be accessed using the globalName value in the esbuild configuration. For example, if the globalName is set to products and your entry file exports an object called productsController and that object has a method called init, then you can access that method in your inline Javascript like this:

<script>
products.init();
</script>

Importing other files

You can import other files in your code.

ES Modules importing

You can import files with the import statement. The file name should end in .js and the file should be an ES module.

import { myFunction } from './my-file.js';

You can also import a default export.

import myFunction from './my-file.js';

CommonJS importing

It's recommended that if you export something in a file that the file is a Common JS file with .cjs as the file extension. You would use module.exports to export your variable.

If you keep your file as a .js file and use exports.default then you have to use .default when accessing the imported variable. That's why it's better to use the CommonJS module.exports.

Beware of circular import/require

If you import a file with import or require and that file imports the file that imported it, then you have circular imports. This will cause issues with your code. The most common issue is that the thing you're trying to import will be an empty object.

When there are circular require() calls, a module might not have finished executing when it is returned, which can result in an empty object being returned.

See https://github.com/evanw/esbuild/issues/1894#issuecomment-1035707059, https://github.com/evanw/esbuild/issues/2037, and https://nodejs.org/api/modules.html#modules_cycles for more information.

Bundle the esbuild output with another file

You can bundle the esbuild output with another Javascript file. This is useful if you have a library that you want to include on all pages but you don't want to do another network request from the browser to get it.

To do that you include bundle information in the esbuild configuration.

For example:

{
javascript: {
entryPoints: [
{
in: 'somefile.js',
out: 'out-file-name',
config: { globalName: 'globalVar' },
bundle: {
nodeModules: [
'nouislider/dist/nouislider.min.js'
]
}
}
]
}
}

The above configuration will bundle the esbuild output and the node_modules/nouislider/dist/nouislider.min.js file in the out-file-name.js file.

The bundle configuration is the same as the regular bundle configuration. You can include node_module files and/or other Javascript files in your code.

Here is another example that includes a node_modules file and a few local Javascript files.

{
javascript: {
entryPoints: [
{
in: 'main.js',
out: 'main',
config: { globalName: 'main' },
bundle: {
nodeModules: [
'micromodal/dist/micromodal.min.js'
],
src: [
'form.js',
'script-loader.js'
]
}
}
]
}
}

Web workers with esbuild

Web workers provide a way to run some code outside of the main thread. They are useful for performance improvements. However, they require importing a JS file from a URL. If you don't want to do that then you can use a slightly different format to setup the web worker and the JS code for the web worker can be imported locally into your file.

Our esbuild plugin is based on esbuild-plugin-inline-worker with a few modifications.

Your web worker file name must end in one of the following:

  • .worker.js
  • .worker.cjs
  • .worker.jsx
  • .worker.ts
  • .worker.tsx

In your web worker file have your code without any exports. For example:

// example.worker.js
onmessage = (e) => {
console.log('Received data: ', e.data);
}

postMessage('Some data to send back');

In your file that will use the web worker, import your web worker file.

// example.js

// Import the web worker file. esbuild will convert "Worker" to a web worker Worker constructor.
// You could use anything for the "Worker" variable.
// https://developer.mozilla.org/en-US/docs/Web/API/Worker
import Worker from './example.worker.js';

// Create the web worker object
const myWorker = new Worker();
// use the web worker object
myWorker.postMessage('Hi web worker!');

Linting Javascript

Javascript files are linted with eslint. Only files within the javascript.src directory are linted. This means that you don't have to worry about setting up ignore rules for the build folder or other folders.

You can override the existing eslint configuration with your own configuration in the eslint section of the configuration file.

The base configuration is simple and only includes @aptuitiv/eslint-config-aptuitiv.

This means that you are safe to set any valid eslint configuration and it won't completely overwrite an existing configuration.

Typically, the only configuration that you may need to do is to add some ignore patterns. For example, to ignore the Javascript for fslightbox you might do something like this:

eslint: {
ignores: ['fslightbox.js']
}

(See eslint Ignore Files) for more information.)

The ignore path should start in the javascript.src directory. Assuming that javascript.src equals src/js then the above example would ignore src/js/fslightbox.js.

You can also ignore directories. For example, to ignore the src/js/maps directory you would do this:

eslint: {
ignores: ['maps/']
}

You can ignore both files and directories.

eslint: {
ignores: ['fslightbox.js', 'maps/']
}

If you're just ignoring files, you could also add a .eslintignore file in your project root. But, for consistency with keeping all build functionality in one place, we recommend that you put any eslint ignores in the configuration file as described above. (If you do use the .eslintignore file then be sure to include the javascript.src dirctory in the ignore path.)

Minify Javascript

By default Javascript files are minified when they are processed. We use terser to handle the minification.

The default minify options are:

{
compress: {
defaults: false,
},
mangle: false,
}

You can alter the minify process with the javascript.minify configuration option.

There are two things you can do with the javascript.minify option.

First, you can pass different minify options to terser. Set javascript.minify to be an object containing any valid terser options and they will be merged with the existing options.

{
javascript: {
minify: {
compress: {
collapse_vars: false,
defaults: false,
},
mangle: true,
keep_classnames: true
}
}
}

Secondly, you can cancel the minification by either setting javascript.minify to false, or by settings javascript.minify.compress to false.

{
javascript: {
minify: false
}
}
{
javascript: {
minify: {
compress: false
}
}
}