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.
- 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. - 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.
- 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 thejavascript.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 thenode_modules
folder. The path should start with the package name and not with thenode_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.
- Use ES modules
- 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.
- The file name extension is
.cjs
, which may confuse some of your team. - You can't use web workers with the CommonJS method. The web worker file name must end in
.worker.js
and be imported withimport
. Sinceimport
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 isiife
(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>
Recommended approach
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
}
}
}