Home Manual Reference Source

Project Configuration

These are the settings that will determine how projext will handle your project.

The file must be created on one of the following paths:

  • [YOUR-PROJECT-PATH]/projext.config.js
  • [YOUR-PROJECT-PATH]/config/projext.config.js
  • [YOUR-PROJECT-PATH]/config/project.config.js

projext will evaluate the list of paths and use the first one it finds.

There's no "top level" setting, everything is separated in different scopes relevant to one specific thing:

{
  // Everything related to where your code is and where it will be bundled.
  paths: ...,

  // The default templates for your target settings.
  targetsTemplates: ...,

  // Your targets information - This must be overwritten.
  targets: ...,

  // The settings of the feature that copies files when bundling.
  copy: ...,

  // The settings of the feature the manages your project version.
  version: ...,

  // The path to custom plugins projext should load
  plugins: ...,

  // Miscellaneous settings.
  others: ...,
}

paths

This setting is all about where your code is located and where it will be bundled:

{
  paths: {
    source: 'src',
    build: 'dist',
    privateModules: 'private',
  }
}

source

The directory, relative to your project path, where your targets code is located. On the documentation is often referred as the "source directory".

build

The directory, relative to your project path, where your targets bundled code will be located. On the documentation is often referred as the "distribution directory".

privateModules

This is for the feature that copies when bundling. In case you are using the feature to copy an npm module that, let's say, is not published, projext will save that module (without its dependencies) on that folder.

targetsTemplates

There was no way to have "smart defaults" for targets and at the same time allow projext an unlimited amount of targets, and that's why the this setting exists.

The targets will extend the template which name is the same as their type property:

{
  node: ...,
  browser: ...,
}

Since there are a lot of settings for the templates, will divide them by type and see each one on detail.

node

{
  type: 'node',
  bundle: false,
  transpile: false,
  engine: null,
  hasFolder: true,
  createFolder: false,
  folder: '',
  entry: { ... },
  output: { ... },
  sourceMap: { ... },
  inspect: { ... },
  css: { ... },
  includeModules: [],
  excludeModules: [],
  includeTargets: [],
  runOnDevelopment: false,
  watch: { ... },
  babel: { ... },
  flow: false,
  typeScript: false,
  library: false,
  libraryOptions: { ... },
  cleanBeforeBuild: true,
  copy: [],
  dotEnv: { ... },
}

bundle

Default value: false

Whether or not the target needs to be bundled. Yes, it's kind of ironic that a tool that aims to simplify bundling would have an option like this, but there are a few scenarios where this may be useful:

  • You are bundling a frontend while you have your backend running on Node, you can bundle your frontend and just copy your backend.
  • You have no frontend target and you are using projext just to organize, run and prepare the distributable files.

If the value is false, when running on a development environment, and if the target doesn't need transpilation, the code won't be moved to the distribution directory.

transpile

Default value: false

This option is kind of tied to the previous one: You may not want to bundle your Node target, but you can transpile it with Babel if you want to use a feature not yet supported by the runtime.

engine

Default value: null

In case bundle is true, this will tell projext which build engine you are going to bundle the code with.

If not overwritten, the value of this setting will be decided by projext when loading the configuration: It will take a list of known engines (webpack and Rollup) and check if any of them was loaded as a plugin.

This is the list of known build engines plugins you can install:

hasFolder

Default value: true

Whether your target code is on a sub folder of the source directory (src/[target-name]/) or the contents of the source directory are your target code (useful when working with a single target).

createFolder

Default value: false

Whether or not to create a folder for your targets code on the distribution directory when the target is bundled/copied.

folder

Default value: ''

If either hasFolder or createFolder is true, this can be used to specify a different folder name than the target's name.

entry

Default value

{
  default: 'index.js',
  development: null,
  production: null,
}

This object is the one that tells projext which is the main file (executable) of your target for each specific environment. If you set null to an entry for an specific environment, it will fallback to the value of the default setting.

output

Default value:

{
  default: {
    js: '[target-name].js',
    fonts: 'statics/fonts/[name]/[name].[hash].[ext]',
    css: 'statics/styles/[target-name].[hash].css',
    images: 'statics/images/[name].[hash].[ext]',
  },
  development: {
    fonts: 'statics/fonts/[name]/[name].[ext]',
    css: 'statics/styles/[target-name].css',
    images: 'statics/images/[name].[ext]',
  },
  production: null,
}

This tells projext where to place the files generated while bundling on each environment, depending on the file type.

You can use the following placeholders:

  • [target-name]: The name of the target.
  • [hash]: A random hash generated for cache busting.
  • [name]: The file original name (Not available for css and js).
  • [ext]: The file original extension (Not available for css and js).

sourceMap

Default value:

{
  development: false,
  production: true,
}

Whether or not to disable source map generation for each environment.

inspect

Default value:

{
  enabled: false,
  host: '0.0.0.0',
  port: 9229,
  command: 'inspect',
  ndb: false,
}

These options allow you to enable and customize the Node inspector for debugging your target code.

inspect.enabled

Whether or not the inspector should be enabled when the target is run for development. You can also leave this as false and force it using the inspect command or the --inspect flag on the run and build commands.

inspect.host

The native Node inspector uses a web socket so it can be accessed as a remote connection from the Chrome Developer tools. This setting is for the socket hostname.

inspect.port

The port the socket for the inspector will use.

inspect.command

The "inspect flag" that will be used to enabled the inspector. It can be either inspect or inspect-brk. More information about this on the Node documentation.

inspect.ndb

Whether or not to use the new Google's ndb. Enabling this setting will make projext ignore the host, port and command as ndb is its own executable.

Since ndb is experimental and requires Node 8 or higher, it's not included by projext automatically, so in order to enable it and avoid errors, you should run on a environment with Node 8 (or higher) and ndb should be installed (local or global, it doesn't matter).

css

Default value:

{
  modules: false,
}

These options help you customize the way the bundling process handles your CSS code.

css.modules

Whether or not your application uses CSS Modules. If this is enabled, all your styles will be prefixed with a unique identifier.

includeModules

Default value: []

This setting can be used to specify a list of node modules you want to process on your bundle.

For example, let's say you are using a library that exports a native Class that you are extending, but you are transpiling for an environment that doesn't support native Classes; you can add the name of the module on this setting and projext will include it on its bundling process and transpile it if needed.

At the end of the process, those names are converted to regular expressions, so you can also make the name a expression, while escaping especial characters of course.

excludeModules

Default value: []

This setting can be used to specify a list of modules that should never be bundled. By default, projext will exclude all the dependencies from the package.json, but if you import modules using a sub path (like colors/safe instead of colors), you need to specify it on this list so the build engine won't try to put it inside the bundle it.

includeTargets

Default value: []

This setting can be used to specify a list of other targets you want to process on your bundle.

For example, you have two targets, let's call them frontend and backend, that share some functionality and which code needs to be transpiled/processed. Since projext define the paths for transpilation/processing to match each target's directory, the wouldn't be able to use shared code between each other.

You have two possible solutions now, thanks to includeTargets: You can either add the other target name on each includeTargets setting, or define a third shared target that both have on the setting.

runOnDevelopment

Default value: false

This tells projext that when the target is builded (bundled/copied) on a development environment, it should execute it.

When the target needs to be bundled, it will relay on the build engined to do it, otherwise, projext will use its custom implementation of nodemon for watching and, if needed, transpile your target code.

watch

Default value:

{
  development: false,
  production: false,
}

Using this flags, you can tell projext to always watch your files when building for an specific environment.

babel

Default value:

{
  features: {
    classProperties: false,
    decorators: false,
    dynamicImports: true,
    objectRestSpread: false,
  },
  nodeVersion: 'current',
  env: {},
  overwrites: {},
}

These options are used in the case the target needs to be bundled or transpiled, to configure Babel:

babel.features

This object can be used to enable/disable the Babel plugins projext includes:

If you need other plugins, they can be included on the overwrites option.

babel.nodeVersion

When building the Babel configuration, projext uses the @babel/preset-env to just include the necessary stuff. This setting tells the preset the version of Node it should "complete".

babel.env

Custom settings that projext will use as base when generating the ones for the @babel/preset-env.

babel.overwrites

If you know how to use Babel and need stuff that is not covered by projext, you can use this setting to overwrite/add any value you may need.

flow

Default value: false

Whether or not your target uses flow. This will update the Babel configuration in order to add support and, in case it was disabled, it will enable transpilation.

typeScript

Default value: false

Whether or not your target uses TypeScript. This will update the Babel configuration in order to add support and, in case it was disabled, it will enable transpilation.

library

Default value: false

If the project is bundled, this will tell the build engine that it needs to be builded as a library to be required.

libraryOptions

Default value:

{
  libraryTarget: 'commonjs2',
}

In case library is true, these options are going to be used by the build engine to configure your library:

libraryOptions.libraryTarget

How the library will be exposed: commonjs2 or umd.

Since this was built based on the webpack API, if you are using it as a build engine, you can set any libraryTarget that webpack supports. The ones mentioned above will be the ones projext will support for all the other build engines with different APIs.

cleanBeforeBuild

Default value: true

Whether or not to remove all code from previous builds from the distribution directory when making a new build.

copy

Default value: []

A list of files to be copied during the bundling process. It can be a list of file paths relative to the target source directory, in which case they'll be copied to the target distribution directory root; or a list of objects with the following format:

{
  from: 'path/relative/to/the/source/directory.txt',
  to: 'path/relative/to/the/distribution/directory.txt',
}

This is different from the main copy feature as this is specific to targets and you may require it for your app to work. For example: You may use this setting to copy a manifest.json for your PWA while you can use the main copy feature for the package.json or an .nvmrc, things you need for distribution.

dotEnv

Default value:

{
  enabled: true,
  files: [
    '.env.[target-name].[build-type]',
    '.env.[target-name]',
    '.env.[build-type]',
    '.env',
  ],
  extend: true,
}

These options are used by both projext and the build engine in order to load environment variables from an "environment file". The variables are parsed with dotenv and expanded with dotenv-expand.

dotEnv.enabled

Whether or not the feature is enabled.

dotEnv.files

The list of files projext will try to find in order to load the variables. Based on the value of extend, the way projext will process them may change.

dotEnv.extend

Whether or not projext should merge all the variables from all the files it can find.

Take for example the following list of files:

[
  '.env.[target-name].[build-type],
  '.env.[target-name]',
]

If extend is set to true and both files exist, projext will load .env.[target-name] as the first file and then merge the values from .env.[target-name].[build-type] on top of it.

If extend is set to false, projext will use the first file it can find.

browser

{
  type: 'browser',
  engine: null,
  hasFolder: true,
  createFolder: true,
  folder: '',
  entry: { ... },
  output: { ... },
  sourceMap: { ... },
  html: { ... },
  css: { ... },
  includeModules: [],
  excludeModules: [],
  includeTargets: [],
  uglifyOnProduction: true,
  runOnDevelopment: false,
  watch: { ... },
  babel: { ... },
  flow: false,
  typeScript: false,
  library: false,
  libraryOptions: { ... },
  cleanBeforeBuild: true,
  copy: [],
  dotEnv: { ... },
  devServer: { ... },
  configuration: { ... },
}

engine

Default value: null

This will tell projext which build engine you are going to bundle the target code with.

If not overwritten, the value of this setting will be decided by projext when loading the configuration: It will take a list of known engines (webpack and Rollup) and check if any of them was loaded as a plugin.

This is the list of known build engines plugins you can install:

hasFolder

Default value: true

Whether your target code is on a sub folder of the source directory (src/[target-name]/) or the contents of the source directory are your target code (useful when working with a single target).

createFolder

Default value: false

Whether or not to create a folder for your targets code on the distribution directory when the target is bundled/copied.

folder

Default value: ''

If either hasFolder or createFolder is true, this can be used to specify a different folder name than the target's name.

entry

Default value

{
  default: 'index.js',
  development: null,
  production: null,
}

This object is the one that tells projext which is the main file (the one that fires the app) of your target for each specific environment. If you set null to an entry for an specific environment, it will fallback to the value of the default setting.

output

Default value:

{
  default: {
    js: 'statics/js/[target-name].[hash].js',
    fonts: 'statics/fonts/[name]/[name].[hash].[ext]',
    css: 'statics/styles/[target-name].[hash].css',
    images: 'statics/images/[name].[hash].[ext]',
  },
  development: {
    js: 'statics/js/[target-name].js',
    fonts: 'statics/fonts/[name]/[name].[ext]',
    css: 'statics/styles/[target-name].css',
    images: 'statics/images/[name].[ext]',
  },
  production: null,
}

This tells projext where to place the files generated while bundling on each environment, depending on the file type.

You can use the following placeholders:

  • [target-name]: The name of the target.
  • [hash]: A random hash generated for cache busting.
  • [name]: The file original name (Not available for css and js).
  • [ext]: The file original extension (Not available for css and js).

sourceMap

Default value:

{
  development: false,
  production: true,
}

Whether or not to disable source map generation for each environment.

html

Default value:

{
  default: 'index.html',
  template: null,
  filename: null,
}

In the case the target is an app, these are the options for the html file that will include the bundle <script />; and if your target is a library, this can be used to test your library.

html.default

This would be the fallback if either template or filename is null.

html.template

The file inside your target source that will be used to generate the html.

html.filename

The file that will be generated when your target is bundled. It will automatically include the <script /> tag to the generated bundle.

css

Default value:

{
  modules: false,
  inject: false,
}

These options help you customize the way the bundling process handles your CSS code.

css.modules

Whether or not your application uses CSS Modules. If this is enabled, all your styles will be prefixed with a unique identifier.

css.inject

If this setting is set to true, instead of generating a CSS file with your styles, they'll be dynamically injected on HTML when the bundle gets executed.

includeModules

Default value: []

This setting can be used to specify a list of node modules you want to process on your bundle.

For example, let's say you are using a library that exports a native Class that you are extending, but you are transpiling for a browser that doesn't support native Classes; you can add the name of the module on this setting and projext will include it on its bundling process and transpile it if needed.

At the end of the process, those names are converted to regular expressions, so you can also make the name a expression, while escaping especial characters of course.

excludeModules

Default value: []

This setting can be used to specify a list of modules that should never be bundled.

includeTargets

Default value: []

This setting can be used to specify a list of other targets you want to process on your bundle.

For example, you have two targets, let's call them frontend and backend, that share some functionality and which code needs to be transpiled/processed. Since projext define the paths for transpilation/processing to match each target's directory, the wouldn't be able to use shared code between each other.

You have two possible solutions now, thanks to includeTargets: You can either add the other target name on each includeTargets setting, or define a third shared target that both have on the setting.

uglifyOnProduction

Default value: true

When a bundle is created, this setting will tell the build engine whether to uglify the code for production or not.

This can be useful for debugging production code.

runOnDevelopment

Default value: false

This will tell the build engine that when you build the target for a development environment, it should bring up an http server to "run" your target.

watch

Default value:

{
  development: false,
  production: false,
}

Using this flags, you can tell projext to always watch your files when building for an specific environment.

babel

Default value:

{
  features: {
    classProperties: false,
    decorators: false,
    dynamicImports: true,
    objectRestSpread: false,
  },
  browserVersions: 2,
  mobileSupport: true,
  polyfill: true,
  env: {},
  overwrites: {},
}

These options are used by the build engine to configure Babel:

babel.features

This object can be used to enable/disable the Babel plugins projext includes:

If you need other plugins, they can be included on the overwrites option.

babel.browserVersions

When building the Babel configuration, projext uses the @babel/preset-env to just include the necessary stuff. This setting tells how many old versions of the major browsers the target needs transpilation for.

Major browsers: Firefox, Chrome, Safari and Edge.

babel.mobileSupport

If true, the configuration will add to the list of major browsers iOS and Android.

babel.polyfill

Whether or not the configuration for the @babel/preset-env should include the settings for useBuiltIns and corejs.

Something that should be noted is that if babel.polyfill is set to true, projext will set useBuiltIns to usage; if you want to change it to entry, you have to change it using babel.env (explained below) and manually import core-js/stable and regenerator-runtime/runtime. If you do it, you can use the same semver range projext uses so npm/yarn can resolve them to the ones installed by projext.

babel.env

Custom settings that projext will use as base when generating the ones for the @babel/preset-env.

babel.overwrites

If you know how to use Babel and need stuff that is not covered by projext, you can use this setting to overwrite/add any value you may need.

flow

Default value: false

Whether or not your target uses flow. This will update the Babel configuration in order to add support for it.

typeScript

Default value: false

Whether or not your target uses TypeScript. This will update the Babel configuration in order to add support for it.

library

Default value: false

This will tell the build engine that it needs to be builded as a library to be required.

libraryOptions

Default value:

{
  libraryTarget: 'umd',
  compress: false,
}

In case library is true, these options are going to be used by the build engine to configure your library:

libraryOptions.libraryTarget

How the library will be exposed: commonjs, umd or window.

Since this was built based on the webpack API, if you are using it as a build engine, you can set any libraryTarget that webpack supports. The ones mentioned above will be the ones projext will support for all the other build engines with different APIs.

libraryOptions.compress

Whether or not to use gzip compression on the generated library file.

cleanBeforeBuild

Default value: true

Whether or not to remove all code from previous builds from the distribution directory when making a new build.

copy

Default value: []

A list of files to be copied during the bundling process. It can be a list of file paths relative to the target source directory, in which case they'll be copied to the target distribution directory root; or a list of objects with the following format:

{
  from: 'path/relative/to/the/source/directory.txt',
  to: 'path/relative/to/the/distribution/directory.txt',
}

This is different from the main copy feature as this is specific to targets and you may require it for your app to work. For example: You may use this setting to copy a manifest.json for your PWA while you can use the main copy feature for the package.json or an .nvmrc, things you need for distribution.

dotEnv

Default value:

{
  enabled: true,
  files: [
    '.env.[target-name].[build-type]',
    '.env.[target-name]',
    '.env.[build-type]',
    '.env',
  ],
  extend: true,
}

These options are used by both projext and the build engine in order to load environment variables from an "environment file". The variables are parsed with dotenv and expanded with dotenv-expand.

dotEnv.enabled

Whether or not the feature is enabled.

dotEnv.files

The list of files projext will try to find in order to load the variables. Based on the value of extend, the way projext will process them may change.

dotEnv.extend

Whether or not projext should merge all the variables from all the files it can find.

Take for example the following list of files:

[
  '.env.[target-name].[build-type],
  '.env.[target-name]',
]

If extend is set to true and both files exist, projext will load .env.[target-name] as the first file and then merge the values from .env.[target-name].[build-type] on top of it.

If extend is set to false, projext will use the first file it can find.

devServer

Default value:

{
  port: 2509,
  reload: true,
  open: true,
  host: 'localhost',
  ssl: {
    key: null,
    cert: null,
    ca: null,
  },
  proxied: { ... },
  historyApiFallback: true,
}

These are the options for the http server projext will use when running the target on a development environment.

devServer.port

The server port.

devServer.reload

Whether or not to reload the browser when the code changes.

devServer.open

Whether or not to open the browser when server is ready.

devServer.host

The dev server hostname.

devServer.ssl

This allows you to set your own SSL certificates in order to run the dev server over HTTPS. The paths must be relative to your project root directory, for example:

ssl: {
  key: 'ssl-files/server.key',
  cert: 'ssl-files/server.crt',
  ca: 'ssl-files/ca.pem',
}

devServer.proxied

Default value:

{
  enabled: false,
  host: null,
  https: null,
}

When the dev server is being proxied (using nginx for example), there are certain functionalities, like hot module replacement and live reload that need to be aware of this, so you need to use these options:

  • enabled: Whether the server is being proxied or not.
  • host: The hostname used. If null, it will use the same as devServer.host.
  • https: Whether or not the server is being proxied over https. This settings has a boolean value, but if you let it as null it will set its value based on devServer.ssl, if you added the certificates it will be true, otherwise false.

devServer.historyApiFallback

Default value: true

Whether or not to redirect the browser back to the root whenever a path can't be found.

configuration

Default value:

{
  enabled: false,
  default: null,
  path: 'config/',
  hasFolder: true,
  defineOn: 'process.env.CONFIG',
  environmentVariable: 'CONFIG',
  loadFromEnvironment: true,
  filenameFormat: '[target-name].[configuration-name].config.js',
}

These are the settings for the feature that allows a browser target to have a dynamic configuration file.

For more precise information, check the document about Browser configuration.

configuration.enabled

Whether or not the feature is enabled.

configuration.default

The default configuration. If none is specified, when the target is builded, it will try to use [target-name].config.js, located on the configuration folder.

configuration.path

The path where the configuration files are located.

configuration.hasFolder

Whether or not there's a folder with the target name on the configuration path.

configuration.defineOn

The name of the variable where the configuration is going to be replaced on your code when bundled.

configuration.environmentVariable

The name of the environment variable projext will check when building the target in order to load a dynamic configuration.

configuration.loadFromEnvironment

Whether or not projext should check for the environment variable value.

configuration.filenameFormat

The name format of the configuration files.

targets

This setting is an empty object because this is the only required setting. This is where you'll add your target(s) information, for example:

{
  targets: {
    backend: {
      type: 'node',
    },
    frontend: {
      type: 'browser',
    },
  }
}

copy

These settings are for the feature that enables projext to copy files when building targets:

{
  enabled: false,
  items: [],
  copyOnBuild: { ... },
}

enabled

Default value: false

Whether or not the feature is enabled.

items

Default value: []

A list of files and/or directories that will be copied. All with paths relative to the project directory.

copyOnBuild

Default value:

{
  enabled: true,
  onlyOnProduction: true,
  targets: [],
}

Since the feature is also available through the projext CLI, you can configure how the feature behaves when building:

copyOnBuild.enabled

Default value: true

Whether or not to copy the files when building. If disabled, you can use the CLI to copy the files.

copyOnBuild.onlyOnProduction

Default value: true

This tells projext if the files should be copied only when building for production, or if it should do it for development too.

copyOnBuild.targets

Default value: []

This can be used to specify the targets that will trigger the feature when builded. If no target is specified, the feature will be triggered by all the targets.

version

These settings are for the feature that manages your project version:

{
  defineOn: 'process.env.VERSION',
  environmentVariable: 'VERSION',
  revision: { ... },
}

defineOn

Default value: process.env.VERSION

The name of the variable where the version is going to be replaced on your code when bundled.

environmentVariable

Default value: VERSION

The name of the environment variable projext should check to get the project version.

revision

Default value:

{
  enabled: false,
  copy: true,
  filename: 'revision',
  createRevisionOnBuild: { ... },
}

This is like a sub-feature. A revision file is a file that contains the version of your project. This is useful when deploying the project to an environment where you have no access to the environment variable.

The way the revision file works is by first checking if the environment variable is available and, if not, it will check if the project is on a GIT repository and try to get the hash of the last commit.

revision.enabled

Default value: false

Whether or not the revision file feature is enabled.

revision.copy

Default value: false

Whether or not to copy the revision file when the project files are being copied to the distribution directory.

revision.filename

Default value: revision

The name of the revision file.

revision.createRevisionOnBuild

Default value:

{
  enabled: true,
  onlyOnProduction: true,
  targets: [],
}

Since the feature is also available through the projext CLI, you can configure how the feature behaves when building:

revision.createRevisionOnBuild.enabled

Default value: true

Whether or not to create the file when building. If disabled, you can use the CLI to copy the files.

revision.createRevisionOnBuild.onlyOnProduction

Default value: true

This tells projext if the file should be created only when building for production, or if it should do it for development too.

revision.createRevisionOnBuild.targets

Default value: []

This can be used to specify the targets that will trigger the feature when builded. If no target is specified, the feature will be triggered by all the targets.

plugins

To load custom plugins.

{
  enabled: true,
  list: [],
}

enabled

Default value: true

Whether or not custom plugins should be loaded.

list

Default value: []

A list of plugin paths relative to the project root directory. Those files can export a single function or a function called plugin in order to be loaded.

For more precise information, check the document about creating plugins.

others

Miscellaneous options.

{
  findTargets: { ... },
  watch: { ... },
  nodemon: { ... },
}

findTargets

Default value:

{
  enabled: true,
}

By default, projext will look in your source directory and try to identify as much information as possible about your target(s), but if for some reason you don't want it to do it, you can use this setting to disable that functionality.

findTargets.enabled

Default value: true

Whether or not you want projext to read your project files and try to assume information about your targets.

watch

Default value:

{
  poll: true,
}

This is used by projext to configure watchpack, which is used to watch Node files that need to be transpiled.

The reason is outside the targetsTemplate.node is because this can be used for any other plugin that watches the file system.

watch.poll

Default value: true

Whether or not to use polling to get the changes on the file system, and if so, it can also be used to specify the ms interval.

nodemon

Default value:

{
  legacyWatch: false,
}

This is used by projext to configure nodemon, which is used to execute and watch Node targets.

nodemon.legacyWatch

Default value: false

Whether or not to enable the nodemon legacy watch mode for systems where the refresh doesn't work. More information check the nodemon documentation.

projext CLI

The projext Command-Line Interface allows you to tell projext which tasks you want to execute and for which targets.

You can run this commands with either yarn, npx or by using a package.json script.

Available commands

Build targets

It builds a target and moves it bundle to the distribution directory.

projext build [target] [--type [type]] [--watch] [--run] [--inspect] [--analyze]
  • target: The name of the target you intend to build. If no target is specified, projext will try to use the default target (the one with the project's name or the first on an alphabetical list).
  • type: Which build type: development (default) or production.
  • watch: Watch the target files and update the build. If the target type is Node and it doesn't require bundling nor transpiling, it won't do anything.
  • run: Run the target after the build is completed. It only works when the build type is development.
  • inspect: Enable the Node inspector. It only works with the run flag and if the target type is node.
  • analyze: Enable the bundle analyzer of the build engine. It only works on browser targets o Node targets with bundle set to true.

Watching a target

It tells projext to watch your target files and update the build if they change.

projext run [target]
  • target: The name of the target you intend to build and watch. If no target is specified, projext will try to use the default target (the one with the project's name or the first on an alphabetical list).

This is basically an alias of projext build that uses the --watch flag by default.

Running a target

If the target is a Node app, it will execute it, otherwise, it will bring up an http server to "run" your target.

projext run [target] [--inspect]
  • target: The name of the target you intend to build and run. If no target is specified, projext will try to use the default target (the one with the project's name or the first on an alphabetical list).
  • inspect: Enable the Node inspector. It only works if the target type is node.

This is basically an alias of projext build that uses the --run flag by default.

Inspecting a Node target

If the target is a Node app, it will execute it and enable the Node inspector.

projext inspect [target]
  • target: The name of the target you intend to build, run and inspect. If no target is specified, projext will try to use the default target (the one with the project's name or the first on an alphabetical list).

This is basically an alias of projext build that uses the --run and --inspect flags by default.

Analyzing a target bundle

This is for bundled targets, it tells the build engine to use the analyzer and show the stats for the generated bundle.

projext analyze [target] [--type [type]]
  • target: The name of the target you intend to analyze. If no target is specified, projext will try to use the default target (the one with the project's name or the first on an alphabetical list).
  • type: Which build type: development (default) or production.

This is basically an alias of projext build that uses the --analyze flag by default.

Cleaning previous builds

Removes the files from previous builds from the distribution directory.

projext clean [target] [--all]
  • target: The name of the target you intend to remove builds from. If no target is specified, projext will try to use the default target (the one with the project's name or the first on an alphabetical list).
  • all: Instead of just removing a target files, it removes the entire distribution directory.

This gets automatically called when building if the target cleanBeforeBuild setting is true.

Copy the project files

If the feature is enabled (check the project configuration document), this will copy the files and/or directories specified on the feature settings to the distribution directory.

projext copy-project-files

This gets automatically called when building if the feature is configured to run when building.

Create the revision file

If the feature is enabled (check the project configuration document), this will create the revision file with the project version.

projext create-revision

This gets automatically called when building if the feature is configured to run when building.

Read the project settings

It logs all the project settings on the console. You can also specify a directory-like path to access specific settings.

projext info [path]
  • path: A directory-like path for an specific setting, for example: targetsTemplates/browser/html. If no path is specified, it will log all the project settings.

Generate resources

projext zero configuration assumes a lot of things about your project in order to run it without a configuration file: Your target(s) settings and, for browser targets, the default HTML.

This command allows you to write down those resources on your project so you can manually modify them:

projext generate [resource] [options]

Resources:

  • config: Writes a configuration file with your target information.
  • html: Writes a browser target default HTML file.

For more information about the generators, please check the Zero configuration document.

Overwriting projext

projext was built on the idea that everything could be overwritten, so if the default functionalities don't cover all your project scenarios, you could easily overwrite a service and make it work the way you like (and hopefully publish it as a plugin later).

All the project structure was built using Jimple, a port of Pimple Dependency Injection container for Node, and EVERYTHING is registered on the container. You can simple set your own version of a service with the same name in order to overwrite it.

The way you get access to the container is by creating a file called projext.setup.js on your project root directory, there you'll create your own instance of projext, register your custom/overwrite services and export it:

// projext.setup.js

// Get the main class
const { Projext } = require('projext');

// Create a new instance
const myProjext = new Projext();

// Overwrite a service
myProjext.set('cleaner', () => myCustomCleaner);

// Export your custom version
module.exports = myProjext;

All projext commands will first check if you have the file and then fallback to the default app.

projext plugins

Creating plugins for projext is really simple as the tool takes care of finding them on your package.json, loading them, allowing them to register services and providing the necessary events so they can interact with the targets.

Naming convention

All projext plugins names should start with projext-plugin-, this allows projext to find them on your project dependencies. Once a plugin is found, projext will be require it and call its exported function with a reference to the dependency container.

Adding services to the container

All the project structure was built using Jimple, a port of Pimple Dependency Injection container for Node, so in oder to register a new service, you should set it on the container.

Let's say the following code is the main file of a plugin:

// Get the service you want to register
const MyService = require('...');

// Export the function that will be called when the plugin is register
module.exports = (projext) => {
  // Set the service on the container
  projext.set('myServiceName', () => new MyService());
}

Events

projext has an events service that is an implementation of wootil's EventsHub and that it uses to emit information events and reduce variables when needed.

...
module.exports = (projext) => {
    ...
    const events = projext.get('events');

    // Add a new listener for a regular event
    events.on('some-event', () => {
      console.log('some-event was fired!');
    });

    // Add a reducer event
    events.on('some-reducer-events', (someConfiguration) => Object.assign({}, someConfiguration, {
      name: 'charito',
    }));
});

Regular events

Revision file creation

  • Name: revision-file-created.
  • Parameters:
    • version: The version written on the file.

This is emitted if the revision file feature is enabled (check the project configuration document) and the command that creates it was called.

Reducer events

List of the project files and/or folders to copy

  • Name: project-files-to-copy.
  • Reduces: The list of files and/or folders to copy.

This event is used if the feature to copy project files is enabled (check the project configuration document) and the command that does the copying is called.

Target information

  • Name: target-load.
  • Reduces: A target information.

This is called when projext loads a new target, after defining its paths and applying its type template.

The list of commands to build a target

  • Name: build-target-commands-list.
  • Reduces: The list of CLI commands projext uses to build a target.
  • Parameters:
    • target: The target information.
    • type: The build type, development or production.
    • run: Whether or not the target will be executed after building.

In order to build targets, projext generates a list of CLI commands that a shell script executes, and this event is called in order to reduce that list.

A target Babel configuration

  • Name: babel-configuration.
  • Reduces: The Babel configuration for an specific target.
  • Parameters:
    • target: The target information.

When building a target, projext will create a Babel configuration based on this settings, then this event is used to reduce that configuration.

projext build engines

A build engine is what takes care of bundling your target code, projext is just the intermediary.

Implementing a build engine

A build engine is a plugin that you need to install on your project, so the first requirement is to install it as a dependency, after that you should change your target(s) engine setting to the name of the build engine you installed.

On an ideal world, that would be enough, but never forget to read the plugin README.

Creating a build engine

You should probably start with the Plugins document to get an idea of how plugins work.

Once you are ready, you need to register a service with the name [your-build-engine-name]BuildEngine and it should implement one required method:

getBuildCommand(target, buildType, forceRun = false)

  • target: The target information.
  • buildType: development or production.
  • forceRun: Whether or not the user intends to run the target after building it, even if the target runOnDevelopment setting is `false.

This should return a string with the command(s) the projext shell script should run in order to generate the bundle.

Let's create a plugin for browserify

The first thing is to create the plugin with the naming convention: projext-plugin-browserify. Now, we'll create a build engine service for it:

This example is just to show how to create the engine, it will only build the target and nothing else. Not even include it on an HTML file.

// src/browserify.js

class BrowserifyBuildEngine {
  getBuildCommand(target, buildType) {
    const entryFile = path.join(target.paths.source, target.entry[buildType]);
    const output = path.join(target.paths.build, target.name);
    return `browserify ${entryFile} -o ${output}.js`;
  }
}

module.exports = BrowserifyBuildEngine;

Really simple, right? Now, assuming the package.json main entry points to src/index.js:

// src/index.js
const BrowserifyBuildEngine = require('./browserify.js');

module.exports = (projext) => {
  projext.set('browserifyBuildEngine', () => new BrowserifyBuildEngine());
};

Done, the only thing to do now is to change a target engine setting to browserify and when building, it will create a bundle using Browserify.

projext browser target configuration

This feature allows you to have dynamic configurations on your browser targets.

For node targets, having multiple configuration files is simple, as they can require files on runtime, but in the case of browser targets, you would probably want to select the configuration you want to use when you bundle the code and be able to include it inside.

That's why, if enabled, projext creates an instance of wootil's AppConfiguration that browser targets can use on the bundling process.

Settings

The settings for this feature are on the target own settings, under the configuration key:

{
  type: 'browser',
  configuration: {
    enabled: false,
    default: null,
    path: 'config/',
    hasFolder: true,
    defineOn: 'process.env.CONFIG',
    environmentVariable: 'CONFIG',
    loadFromEnvironment: true,
    filenameFormat: '[target-name].[configuration-name].config.js',
  },
}

enabled

Whether or not the feature is enabled.

default

The default configuration. It will be the base all the other, "dynamic", configuration will extend.

If not specified, projext will try to load a configuration file called [target-name].config.js, inside the configuration path.

path

The path relative to the root directory where the configurations are located.

hasFolder

If true, projext will append a folder with the name of the target on the configurations path.

defineOn

The name of a variable that, when the target is builded, will be replaced with the configuration object.

environmentVariable

The name of an environment variable where projext will check for a configuration name.

loadFromEvironment

Whether or not projext should check the environment variable. This is for cases in which loading the default configuration is enough for your project.

filenameFormat

The name format of the configuration files. [configuration-name] will be replaced with the value of the environment variable.

Using a configuration

First, let's assume the following things:

  • You set enabled to true.
  • You left all the other default values.
  • You target is named myapp

Now, you should be able to use it by sending the environment variable before the projext command:

CONFIG=debug [projext-command-to-build-a-target]

This will load config/myapp/myapp.config.js and then config/myapp/myapp.debug.js.

Code splitting

This feature allows you to split your bundles on smaller pieces and then load those pieces when you need them (basically, "lazy loading" parts of your bundle).

Let's say you are building an application with an admin panel, you could use code splitting for the route/module that handles the admin panel and only load it if the user goes to an /admin route. Doing something like this will make the initial load and the initialization of the app faster, providing a better experience for the user.

How does it work?

Instead of importing a file on the top of your file, you use import as a function and wait for a Promise with the module's content to be resolved.

Using the same example as above, here's some code to better understand it: You have a module that exports a function that will return a component for a given route:

import Home from './components/home';
import Admin from './components/admin';

const getComponentForRoute = (route) => {
  switch(route) {
  case '/admin':
    return Admin;
  default:
    return Home;
  }
};

export default getComponentForRoute;

Easy enough, if the route is /admin, you return the admin panel component, which was imported at the top.

Let's switch it a little bit in order to import the admin panel only when the user goes to /admin, thus, splitting it on different bundle:

import Home from './components/home';

const getComponentForRoute = (route) => {
  switch(route) {
  case '/admin':
    return import('./components/admin');
  default:
    return Promise.resolve(Home);
  }
};

export default getComponentForRoute;

There are 3 changes here:

  1. The import for the admin component is not longer on the top.
  2. The case for /admin now returns a call to the import function.
  3. The default case returns Home on an already resolved Promise.

First and second go together: By using the import function instead of doing the regular import declaration, we tell the bundle engine that we don't need that file right now, but that it will be required later, so the engine will create a smaller bundle with that code.

Third, why do we return Home on a Promise? Well, the import function returns a promise as the other bundle is loaded asynchronously, we should try to keep a consistent signature for the function, so the part of the app that implements it won't need to know whether the component was loaded from this bundle or a different one.

How to use it with projext

Adding code splitting to your targets is really simple, you just need to define a jsChunks ("chunks" are the smaller bundles with the code that will be lazy loaded) with a value of true on you target output settings:

...
output: {
  default: {
    ...
    jsChunks: true,
  },
  ...
}

In the example above, you define it on the default so both production and development will inherit it.

This will tell the bundle engine to use code splitting and that the chunks should be on the same directory as the main bundle and that the name format will be [original-bundle-name].[chunk-name].js.

You can also set jsChunks as a string and send a path and name format for your chunks:

...
output: {
  default: {
    ...
    jsChunks: 'statics/js/[target-name].[hash].[name].js',
  },
  ...
}

The special placeholder there is [name], which will be replaced with the chunk name, decided by the bundle engine.

That's all, enable jsChunks and enjoy code splitting!

Currently, the webpack bundle engine will enable code splitting by default (you can even use the special webpackChunkName comment to set a name for it) but other engines still need the property in order to make it work. So, if you are using webpack as an engine, you can still enjoy zero configuration and take advantage of this feature.