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:
- webpack:
projext-plugin-webpack
- Rollup:
projext-plugin-rollup
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 forcss
andjs
).[ext]
: The file original extension (Not available forcss
andjs
).
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 extend
ing, but you are transpiling for an environment that doesn't support native Class
es; 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:
classProperties
(disabled):@babel/plugin-proposal-class-properties
.decorators
(disabled):@babel/plugin-proposal-decorators
.dynamicImports
(enabled):@babel/plugin-syntax-dynamic-import
.objectRestSpread
(enabled):@babel/plugin-proposal-object-rest-spread
.
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 require
d.
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:
- webpack:
projext-plugin-webpack
- Rollup:
projext-plugin-rollup
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 forcss
andjs
).[ext]
: The file original extension (Not available forcss
andjs
).
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 extend
ing, but you are transpiling for a browser that doesn't support native Class
es; 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:
classProperties
(disabled):@babel/plugin-proposal-class-properties
.decorators
(disabled):@babel/plugin-proposal-decorators
.dynamicImports
(enabled):@babel/plugin-syntax-dynamic-import
.objectRestSpread
(enabled):@babel/plugin-proposal-object-rest-spread
.
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 require
d.
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. Ifnull
, it will use the same asdevServer.host
.https
: Whether or not the server is being proxied overhttps
. This settings has a boolean value, but if you let it asnull
it will set its value based ondevServer.ssl
, if you added the certificates it will betrue
, otherwisefalse
.
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) orproduction
. - 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 isnode
. - analyze: Enable the bundle analyzer of the build engine. It only works on browser targets o Node targets with
bundle
set totrue
.
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) orproduction
.
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 istrue
.
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
orproduction
.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
orproduction
.forceRun
: Whether or not the user intends to run the target after building it, even if the targetrunOnDevelopment
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
totrue
. - 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:
- The
import
for the admin component is not longer on the top. - The
case
for/admin
now returns a call to theimport
function. - The default
case
returnsHome
on an already resolvedPromise
.
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.