projext
Bundle and run your javascript project without configuring an specific module bundler.
Introduction
What?
Let's start with this:
- projext is not an alternative to webpack.
- projext is not a module bundler.
Now, this is a tool that allows you to configure a project bundling options on an "almost-human" readable format so you don't have to deal with very complex rules and structures.
projext also has "zero configuration" support so you can start coding right away. Read more about this on the Zero Configuration document.
The idea is to divide your project bundling on a 4 layers architecture:
Layer | |
---|---|
Project configuration | Managed by projext |
Bundler engine | A projext plugin for Webpack/Rollup/Parcel/etc. |
Framework | A projext plugin with the framework settings of the used bundler. |
Others... | Other plugins like a runner tool or bundler analyzer. |
Quick example
You want to create an AngularJS app and you want to bundle it with webpack. You first need the following dependencies:
Then, if you want to use a configuration file, you would write something like this:
module.exports = {
targets: {
browser: {
type: 'browser',
engine: 'webpack',
framework: 'angularjs',
},
},
};
Or you can just create your src/index.js
and start coding:
// src/index.js
import angular from 'angular';
...
projext will look at your code and automatically assume a configuration like the one above.
There are a lot of "smart defaults" on the project configuration, but since this we just want a quick example, we are going to modify just what we need.
That's all you need to do in terms of configuration, after that you can start coding your app.
Why?
Module bundlers have been around for quite some time now and they are amazing tools, they allow us to do so much, from just putting the files together to transpiling, tree shaking, splitting for lazy load, etc.
I've been bundling projects since require.js was popular, and since mid 2016 I've been using webpack for almost everything. You can configure every detail, Babel integration, files optimization, Express middlewares, etc; But after year and half, and more than 20 projects built with it... I'm sick and tired of writing the configuration.
I even wrote a plugin to manage the configurations, but at the end of the day, I always ended up with huge configurations files, tons of rules and a lot of plugins/dependencies that needed to be up to date and comply with each other versions.
I'm well aware of the new features webpack 4 will bring to the table, including all the "smart defaults", and it looks really promising. But I still believe this tool is for a different purpose than just bundling.
At some point I started considering start using Rollup, at least for the non web apps projects. Rollup was getting a lot of traction and since Facebook started adopting it, it got really popular.
I tried it for a npm package project and the results were pretty impressive: It doesn't require a lot of configuration and there's not a lot of boiler plate code on the builds.
I considered migrating a few older projects, but I didn't want to have to go over their configurations, so I just kept it for new projects.
Then, a few months ago, Parcel showed up, and the community was all over it (I know, we are a very hype-driven community :P). And, of course, I wanted to use it, but at that point it was too much. That's when I finally understood the "Javascript Fatigue".
And at that point was when I had the first idea for this project: Preparing boiler plates for different bundlers for different scenarios: library, web page, web app, etc. Yes, it wasn't very original, but it was the start.
As you may have suspected, there were a few issues:
- Hard to maintain: Different projects for the same tool that needed to be up-to-date.
- What about the framework? Yes, frameworks are another big cause of the "Javascript Fatigue", and they all have their unique configuration for specific bundlers.
- I had to align every project to whatever the boiler plate needed in order to work.
But thanks to those problems was that I was able to come with a plan for this project:
- Make a tool that would understand your project configuration on an "almost-human" readable format, meaning, try to move almost every configuration to
boolean
orstring
value and no configuration functions. - Add another layer, on a form of plugin, that would take the project configuration and apply it to a bundler configuration.
- And another layer/plugin that would be the framework implementation for that bundler.
And then I built it. Right now it's only webpack as an bundler engine and AngularJS for webpack as framework, but I'm already building the Rollup engine, adding the React to webpack and planning on then making ports of both AngularJS and React.
My plan is to ask the community for help putting these plugins/recipes together.
Who?
Of course there's no way this will be helpful for everyone: a tool that works as an abstraction of other tool could never cover all the possible scenarios.
This is aimed to those who use bundlers everyday to build web sites, libraries and apps without the need to go to the last optimal detail. If you need to do that, then use the bundler directly, it's the best choice.
Information
- | - |
---|---|
Package | projext |
Description | Bundle your Javascript projects without having to learn how to use a bundler. |
Node Version | >= v10.13.0 |
Usage
Zero configuration
After installing projext and the necessary dependencies for bundling (and framework possibly), you can start coding by just creating a src/index.js
file on your project.
When you run the CLI commands, projext will automatically find your file, it will check its contents and try to determine if you are writing a browser or a Node app based on the modules and syntax you are using.
Read more about projext Zero Configuration
Project configuration
In case you need to overwrite targets settings or enable other projext features, you can create a project configuration file called projext.config.js
.
The project configuration file can be located on the following paths:
projext.config.js
config/projext.config.js
config/project.config.js
projext will evaluate the list of paths and use the first one it finds.
The file needs to export a single object that projext will use to merge on top of the "smart defaults".
For example, you want to enable the feature that executes a target when bundled on development:
module.exports = {
targets: {
myTarget: {
runOnDevelopment: true,
},
},
};
Read more about the project configuration.
Bundling the code
For this example, we are going to assume this is what your targets look like:
module.exports = {
targets: {
backend: {
type: 'node',
},
frontend: {
type: 'browser',
},
},
};
The way you bundle your targets is by using the build
command from the projext CLI:
You can use scripts from the
package.json
,$(npm bin)/
ornpx
too, but for these examples I'll be usingyarn
yarn projext build backend
# or
yarn projext build frontend
Really simple right? For the frontend
target, it will take all the source, bundle it and move it to the distribution directory (dist/
by default, but again, configurable).
For the backend
target it will give you a warning (not cool, I know), because the default build type is for a development environment and we didn't specify that the target needed to be bundled nor that it needed transpilation, so projext doesn't see the need to move it.
By default, projext doesn't bundle nor transpile Node targets code, but you can enable it by changing the
bundle
and/ortranspile
target settings. More about this after the following example.
Now, time to build for production:
yarn projext build backend --type production
# or
yarn projext build frontend --type production
Done, the --type
argument set to production
tells projext that you are preparing a build production, so it will move everything to the distribution directory.
projext and Node apps
By default, projext doesn't bundle nor transpile Node targets code (yes, it sounds ironic) as there's not a lot of advantages on doing it and the support for ES+ syntaxs on Node is pretty great.
When you try to bundle a Node target for development, if you didn't change the settings, you'll get a warning that says that there's no need for it; but if you do it for production, the code will be copied to the distribution directory as you may want to deploy it.
Now, Node targets have two special settings: bundle
and transpile
. With bundle
you can specify whether you want to bundle the entire code on a single file or not; and with transpile
you can tell projext to just transpile the files content using Babel but keeping all the files.
Other features
Running the targets
It's not all about putting all the files together. You can also use projext to run your targets while you code.
For node
targets, it has a custom implementation of nodemon
that will take care of watching and, if needed, transpiling your files while you code.
For browser
targets it uses the bundle engine to run it so it can update your bundle on any change.
You can extend most of the things and overwrite EVERYTHING
The whole tool is 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.
If you haven't tried Jimple, give it a try, it's excellent for organizing your app dependencies and services.
Read more about overwriting projext.
Building plugins is really easy
By default, projext checks your package.json for dependencies which names start with projext-plugin-
, require
them, they need to export a function that will receive the app container as parameter, thus allowing them to listen for events or even overwrite existing services.
For example, you want to create a plugin for browserify (If someone is interested, please go ahead :)):
You would call your plugin projext-plugin-browserify
to assure that projext will pick it and require
it, and then the code would look something like this:
module.exports = (projext) => {
projext.set('browserifyBuildEngine', ...);
};
Browser targets configuration
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.
To enable it, you have to edit your target settings:
module.exports = {
targets: {
frontend: {
type: 'browser',
engine: 'webpack',
configuration: {
enabled: false,
},
},
},
};
That's all you need to enable the feature, the rest is dictated by the setting "smart defaults":
- You target configurations will be on
config/browser/...
. - The default configuration will be loaded from
config/browser/browser.config.js
. - Whenever you write
process.env.CONFIG
on your code, when bundled, it will replaced by the configuration contents. - If you add
CONFIG=xyz
before theprojext build
command, the service will look for a filebrowser.xyz.config.js
and the configuration will be created by extending the default one.
Read more about browser targets configuration
Development
NPM/Yarn Tasks
Task | Description |
---|---|
yarn test |
Run the project unit tests. |
yarn run lint |
Lint the modified files. |
yarn run lint:full |
Lint the project code. |
yarn run docs |
Generate the project documentation. |
yarn run todo |
List all the pending to-do's. |
Testing
I use Jest with Jest-Ex to test the project. The configuration file is on ./.jestrc
, the tests and mocks are on ./tests
and the script that runs it is on ./utils/scripts/test
.
Linting
I use ESlint to validate all our JS code. The configuration file for the project code is on ./.eslintrc
and for the tests on ./tests/.eslintrc
(which inherits from the one on the root), there's also an ./.eslintignore
to ignore some files on the process, and the script that runs it is on ./utils/scripts/lint
.
Documentation
I use ESDoc to generate HTML documentation for the project. The configuration file is on ./.esdocrc
and the script that runs it is on ./utils/scripts/docs
.
To-Dos
I use @todo
comments to write all the pending improvements and fixes, and Leasot to generate a report. The script that runs it is on ./utils/scripts/todo
.