Security News
PyPI Introduces Digital Attestations to Strengthen Python Package Security
PyPI now supports digital attestations, enhancing security and trust by allowing package maintainers to verify the authenticity of Python packages.
buddy is a fast and simple build tool for the web. It compiles source code from higher order JS/CSS/HTML languages, resolves dependencies, and bundles all sources for more efficient delivery to the browser.
version: es5
)import('foo')
and lazy loaded bundlesprocess.env.FOO
["last 2 versions", "iOS >= 7"]
)inline
attribute{FOO}
Install buddy as a devDependency
in your project directory:
$ npm install --save-dev buddy
If you want a global buddy command, install the buddy-cli with
$ npm install --global buddy-cli
Usage: buddy [options] <command> [configpath]
Commands:
build [configpath] build js, css, html, and image sources
watch [configpath] watch js, css, html, and image source files and build changes
deploy [configpath] build compressed js, css, html, and image sources
Options:
-h, --help output usage information
-V, --version output the version number
-c, --compress compress output for production deployment
-g, --grep <pattern> only run build targets matching <pattern>
-i, --invert inverts grep matches
--input input file/directory for simple config-free build
--output output file/directory for simple config-free build
-r, --reload reload all connected live-reload clients on file change during watch
-s, --serve create (or launch) a webserver to serve files during watch
-S, --script run script on build completion
-m, --maps generate js/css source maps
-v, --verbose print all messages for debugging
buddy is configurable via js
or json
formatted configuration files. By default, buddy looks for the nearest buddy.js
, buddy.json
, or package.json
(with a buddy
entry). Alternatively, you can specify the path to your configuration file while running the buddy
command.
Note that, whichever way you configure it, buddy will treat the directory that contains the configuration file as the project root.
Please refer to the annotated configuration guide to see all the different options.
buddy's ability to transform and manipulate different source files is made possible by a flexible plugin system. In fact, all of the core language features are implemented as plugins internally, so there should be very few features that cannot be implemented this way.
One of the most common use cases for extending buddy is to enable working with higher-order JS/CSS/HTML languages. The following plugins can be installed ($ npm install --save-dev {plugin}
) if you prefer not to write vanilla JS/CSS/HTML:
.coffee
source files to .js
.ts
and .tsx
source files to .js
.dust
html template source files to .html
.handlebars
html template source files to .html
.nunjucks
html template source files to .html
.less
source files to .css
.styl
source files to .css
Follow the plugins guide to learn about writing your own.
JS dependencies are declared by use of require()
expressions (or import
statement for es6 modules), and closely follow the module semantics as used in Node.js. This makes it possible to write modules for the browser the same way as you would for Node.js server environments. Although buddy preserves similar author-time semantics, run-time behaviour does differ. In Node.js modules, each file is wrapped in a function closure to provide an isolated scope for module-level variable/function/class declarations, ensuring that there are no conflicts between modules. In the browser, however, wrapping each module in a closure can impose significant start-up cost and overhead. As a result, for performance reasons, buddy flattens all modules into a shared scope, renames all declarations, and inlines all calls to require()
:
{
"buddy": {
"build": [
{
"input": "src/index.js",
"output": "www"
}
]
}
}
// src/index.js
const foo = require('./foo');
console.log(foo());
// src/foo.js
module.exports = function foo () {
return 'foo';
};
Resulting in:
/** BUDDY BUILT **/
// ...boilerplate
(function () {
/*== src/foo.js ==*/
$m['src/foo.js'] = { exports: {} };
$m['src/foo.js'].exports = function foo () {
return 'foo';
};
/*≠≠ src/foo.js ≠≠*/
/*== src/index.js ==*/
$m['src/index.js'] = { exports: {} };
const _srcindexjs_foo = $m['src/foo.js'].exports;
console.log(_srcindexjs_foo());
/*≠≠ src/index.js ≠≠*/
})()
Although these optimizations are possible to apply in most cases, there are two scenarios where buddy needs to de-optimize by wrapping module contents and/or preserving calls to require()
:
require('module-from-another-bundle')
will be preserved as it cannot be safely inlined (read more about working with multiple bundles)require
each other (including several orders removed) will be wrapped in a closure function and lazily evaluated when eventually called with a non-inlined require()
CSS dependencies are declared by use of the @import
statement. buddy replaces these statements with the referenced file contents, inlining a file's dependencies rather than concatenating them:
{
"buddy": {
"build": [
{
"input": "src/index.css",
"output": "www"
}
]
}
}
/* src/index.css */
@import 'foo.css';
body {
color: red;
}
@import './utils/bar.css';
/* src/foo.css */
/* Import from module installed in node_modules */
@import 'normalize.css';
/* src/utils/bar.css*/
p {
color: blue;
}
Resulting in:
/*
normalize.css content here
*/
body {
color: red;
}
p {
color: blue;
}
Note that, while a JS dependency tree can be optimized to avoid duplicates, the cascading nature of CSS requires that dependency order be strictly observed, and as a result, duplicate @import
statements will result in duplicate file content.
Although HTML dependencies are numerous and varied, buddy only manages a specific subset of dependencies that are flagged for inlining. Specifying an inline
attribute on certain tags results in the file contents being copied into the HTML:
{
"buddy": {
"build": [
{
"input": "src/index.html",
"output": "www"
}
]
}
}
<!DOCTYPE html>
<html>
<head>
<link inline rel="stylesheet" href="src/index.css">
<script inline src="src/index.js"></script>
</head>
<body>
<img inline src="src/image.svg">
</body>
</html>
Resulting in:
<!DOCTYPE html>
<html>
<head>
<style>
body {
color: red;
}
</style>
<script>
console.log('foo');
</script>
</head>
<body>
<svg>
<circle cx="50" cy="50" r="25"/>
</svg>
</body>
</html>
Since buddy uses Babel to transform JS sources, it is easy to target a specific version of JavaScript you want to output to. Specifying one or more output versions simply loads the appropriate Babel plugins required to generate the correct syntax. If one or more of the plugins have not yet been installed, buddy will automatically install them to your dev-dependencies
:
{
"buddy": {
"build": [
{
"input": "src/browser.js",
"output": "www",
"version": "es5"
},
{
"input": "src",
"output": "dist",
"bundle": false,
"version": "node6"
}
]
}
}
The following JS version targets are supported:
In addition to generic language/environment versions, buddy also supports browser version targets, and Autoprefixer-style browser list configuration:
{
"buddy": {
"build": [
{
"input": "src/chrome.js",
"output": "www",
"version": {
"chrome": 50
}
},
{
"input": "src/browsers.js",
"output": "www",
"version": ["last 2 versions", "iOS >= 7"]
}
]
}
}
Since buddy uses PostCSS and Autoprefixer to transform CSS sources, it is easy to target specific browser versions (via vendor prefixes) you want to output to:
{
"buddy": {
"build": [
{
"input": "src/index.css",
"output": "www",
"version": ["last 2 versions", "iOS >= 7"]
}
]
}
}
Large JS bundles can be broken up into a collection of smaller bundles by nesting builds. Each build can have one or more children, and any parent modules that are referenced in child builds will not be duplicated:
{
"buddy": {
"build": [
{
"input": "src/libs.js",
"output": "www",
"children": [
{
"input": "src/index.js",
"output": "www"
},
{
"input": "src/extras.js",
"output": "www"
}
]
}
]
}
}
// src/libs.js
const lodash = require('lodash');
// src/index.js
const react = require('react');
// The 'lodash' module will not be included because index.js is a child of libs.js
const lodash = require('lodash');
// src/extras.js
// The 'react' module will be included because extras.js is not a child of index.js
const react = require('react');
// The 'lodash' module will not be included because index.js is a child of libs.js
const lodash = require('lodash');
The same result may also be achieved with dynamic child builds using import()
:
{
"buddy": {
"build": [
{
"input": "src/libs.js",
"output": "www/assets"
}
],
"server": {
"webroot": "www"
}
}
}
// src/libs.js
import('./index.js')
.then((index) => {
console.log('index module loaded');
});
...compiles to:
// www/assets/libs.js
buddyImport('/assets/index-b621480767a88ba492db23fdc85df175.js', 'src/index')
.then((index) => {
console.log('index module loaded');
});
Child builds will be automatically generated and loaded asynchronously at runtime. Note that some environments may require a Promise
polyfill, and that the id's passed to import()
must be statically resolvable strings. It may also be necessary to configure the child bundle url by declaring a webroot
property in buddy.server
config.
A build may be automatically generated based on the content of it's children. For example, to generate a parent bundle based on the of shared dependencies between children, specify a parent build with an input
of 'children:common'
or 'children:shared'
. All dependencies shared between child builds will be moved to the parent bundle:
{
"buddy": {
"build": [
{
"input": "children:shared",
"output": "www/shared.js",
"children": [
{
"input": "src/index.js",
"output": "www"
},
{
"input": "src/extras.js",
"output": "www"
}
]
}
]
}
}
// src/index.js
// The 'lodash' module will be moved to shared.js because it is also used in extras.js
const lodash = require('lodash');
// src/extras.js
// The 'react' module will not be moved to shared.js
const react = require('react');
// The 'lodash' module will be moved to shared.js because it is also used in index.js
const lodash = require('lodash');
Another possiblility is to gather all used node_modules
files into a parent bundle:
{
"buddy": {
"build": [
{
"input": "children:**/node_modules/**/*.js",
"output": "www/shared.js",
"children": [
{
"input": "src/index.js",
"output": "www"
},
{
"input": "src/extras.js",
"output": "www"
}
]
}
]
}
}
// src/index.js
// The 'lodash' module will be moved to shared.js because it is in node_modules
const lodash = require('lodash');
// src/extras.js
// The 'foo' module will not be moved to shared.js
const foo = require('./foo');
// The 'lodash' module will be moved to shared.js because it is in node_modules
const lodash = require('lodash');
Matching patterns follow the rules for glob matching.
Source maps for JS and CSS sources are automatically generated when bundling with the --maps
command flag. Source map files are generated alongside output files with an appended *.map
extension (ex: www/foo.js.map
). You may configure a server.sourceroot
base url to load source map files from, but the source map files must then be manually uploaded/moved.
By default, js modules in a bundle are evaluated in reverse dependency order as soon as the file is loaded, with the input
module evaluated and executed last. Sometimes, however, it is useful to delay evaluation and execution until a later time (so-called lazy evaluation). For example, when loading several bundles in parallel, it may be important to have more control over the order of evaluation:
{
"buddy": {
"build": [
{
"input": "src/libs.js",
"output": "www",
"children": [
{
"input": "src/index.js",
"output": "www",
"bootstrap": false,
"children": [
{
"input": "src/extras.js",
"output": "www",
"bootstrap": false
}
]
}
]
}
]
}
}
// After loading libs.js, index.js, and extras.js in parallel...
// ...guarantee that index.js is evaluated before extras.js
require('src/index');
require('src/extras');
All references to process.env.FOO
variables are automatically inlined in JS source files, and all references to {FOO}
variables are automatically inlined in HTML source files.
In addition to all the system variables set before build, the following special variables are set during build:
RUNTIME
: current runtime for browser code (value browser
or server
)BUDDY_{LABEL or INDEX}_INPUT
: input filepath(s) for target identified with LABEL
or INDEX
(value filepath
or filepath,filepath,...
if multiple inputs)BUDDY_{LABEL or INDEX}_INPUT_HASH
: hash(es) of input file(s) for target identified with LABEL
or INDEX
(value xxxxxx
or xxxxxx,xxxxxx,...
if multiple inputs)BUDDY_{LABEL or INDEX}_INPUT_DATE
: timestamp(s) of input file(s) for target identified with LABEL
or INDEX
(value 000000
or 000000,000000,...
if multiple inputs)BUDDY_{LABEL or INDEX}_OUTPUT
: output filepath(s) for target identified with LABEL
or INDEX
(value filepath
or filepath,filepath,...
if multiple outputs)BUDDY_{LABEL or INDEX}_OUTPUT_HASH
: hash(es) of output file(s) for target identified with LABEL
or INDEX
(value xxxxxx
or xxxxxx,xxxxxx,...
if multiple outputs)BUDDY_{LABEL or INDEX}_OUTPUT_DATE
: timestamp(s) of output file(s) for target identified with LABEL
or INDEX
(value 000000
or 000000,000000,...
if multiple outputs)BUDDY_{LABEL or INDEX}_OUTPUT_URL
: url(s) of output file(s) for target identified with LABEL
or INDEX
(value /xxx/xxxx
or /xxx/xxxx,/xxx/xxxx,...
if multiple outputs){
"buddy": {
"build": [
{
"input": "src/index.js",
"output": "www/index-%hash%.js",
"label": "js"
},
{
"input": "src/index.css",
"output": "www/index-%hash%.css",
"label": "css"
},
{
"input": "src/service-worker.js",
"output": "www",
"label": "sw"
}
]
}
}
The last target (labelled sw
) will have access to the unique outputs of the previous targets:
// src/service-worker.js
const VERSION = process.env.BUDDY_SW_INPUT_HASH;
const ASSET_JS = process.env.BUDDY_JS_OUTPUT;
const ASSET_CSS = process.env.BUDDY_CSS_OUTPUT;
...which converts to:
// service-worker.js
const VERSION = 'c71a077b25a6ee790a4ce328fc4a0807';
const ASSET_JS = 'www/index-03d534db2f963c0829b5115cef08fcce.js';
const ASSET_CSS = 'www/index-cf4e0949af42961334452b1e11fe1cfd.css';
Since buddy implements the same dependency resolution semantics as Node.js, it is possible to end up with unwieldy relative paths when referencing files from deeply nested project directories: require('../../../../some-module')
. And as for Node.js, you have a choice between the following two workarounds:
node_modules
directory:project/
node_modules/ (installed with npm)
src/
node_modules/ (manually created)
app/
libs/
$NODE_PATH
environment variable:$ NODE_PATH=./src buddy watch
Allowing you to require('libs/some-module')
from anywhere in your project directory structure.
When writing universal modules for use in both server and browser environments, it is sometimes desirable to specify an alternative entry point for inclusion in the browser. The alternative to the main
package.json parameter is browser
:
{
"name": "myModule",
"version": "1.0.0",
"main": "lib/server.js",
"browser": "lib/browser.js"
}
buddy correctly handles this remapping when resolving node_modules dependencies that use the browser
package.json field. In addition, it is possible to employ more advanced uses to alias files and modules directly in your project:
{
"browser": {
"someModule": "node_modules/someModule/dist/someModule-with-addons.js"
}
}
...or even disable a module completely when bundling for the browser:
{
"browser": {
"someModule": false
}
}
In addition to the standard behaviour of remapping a module to a file path, buddy extends this concept to allow renaming a project module's id reference:
{
"browser": {
"extra-libs": "./src/extra/libs.js"
}
}
// src/index.js
const bar = require('extra-libs'); // Instead of './src/extra/libs'
This can be especially useful when using child bundles.
A React language plugin is provided by default. Just specify react
as a build target version to compile .jsx
files:
{
"buddy": {
"build": [
{
"input": "src/index.js",
"output": "www",
"version": ["es5", "react"]
}
]
}
}
A Flow plugin is provided by default. Just specify flow
as a build target version to strip Flow types from .js
files:
{
"buddy": {
"build": [
{
"input": "src/index.js",
"output": "www",
"version": ["es5", "flow"]
}
]
}
}
Babel is configured via the options.babel
build configuration parameter:
{
"buddy": {
"build": [
{
"input": "src/index.js",
"output": "www",
"options": {
"babel": {
"plugins": [["babel-plugin-transform-es2015-classes", { "loose": false }]],
"presets": ["my-cool-babel-preset"]
}
}
}
]
}
}
PostCSS is configured via the options.postcss
build configuration parameter:
{
"buddy": {
"build": [
{
"input": "src/index.css",
"output": "www",
"options": {
"postcss": {
"plugins": ["postcss-color-function"]
}
}
}
]
}
}
Plugins are configured via the options.{plugin}
build configuration parameter:
{
"buddy": {
"build": [
{
"input": "src/index.js",
"output": "www",
"options": {
"uglify": {
"compressor": {
"drop_debugger": true
}
}
}
},
{
"input": "src/index.css",
"output": "www",
"options": {
"cssnano": {
"normalizeUrl": false
}
}
}
]
}
}
Unique filenames can be automatically generated by including one of two types of token in the output filename:
{
"buddy": {
"build": [
{
"input": "somefile.js",
"output": "somefile-%hash%.js"
},
{
"input": "somefile.css",
"output": "somefile-%date%.css"
}
]
}
}
Unique filenames are generally recommended as a cache optimisation for production deploys, so it's often a good idea to only specify a unique name when compressing:
{
"buddy": {
"build": [
{
"input": "somefile.js",
"output": "www",
"output_compressed": "www/somefile-%hash%.js"
}
]
}
}
Individual builds can be skipped by using the --grep
and --invert
command flags. The --grep
command flag will isolate builds with input
or label
that match the provided pattern, and the --invert
pattern negates the match:
{
"buddy": {
"build": [
{
"input": "src/index.js",
"output": "www",
"label": "js"
},
{
"input": "src/index.css",
"output": "www",
"label": "css"
},
{
"input": "src/images",
"output": "www/images",
"label": "images"
}
]
}
}
# Build everything except 'images'
$ buddy build --invert --grep images
Buddy accepts config files that contain named nested sets, and if one of those sets match the value of process.env.NODE_ENV
, the configuration data used in that set will then be used:
{
"buddy": {
"development": {
"build": {
"input": "src/dev.js",
"output": "www"
}
},
"production": {
"build": {
"input": "src/prod.js",
"output": "www"
}
},
"foo": {
"build": {
"input": "src/foo.js",
"output": "www"
}
}
}
}
# Build 'development' set
$ NODE_ENV=development & buddy build
Custom names may also be used and passed when executing:
$ buddy build foo
When executing the watch
command with the --serve
flag, buddy will rely on the buddy-server plugin to launch a local development server. If the plugin is not already installed, buddy will automatically install it to your dev-dependencies
.
buddy-server has two primary modes:
A default static file server that serves files from a local directory
:
"buddy": {
"server": {
"port": 8000,
"directory": "www"
}
}
Or a custom application server:
"buddy": {
"server": {
"port": 8000,
"file": "./index.js"
}
}
When working with a custom server, you can pass along application environment variables and flags to the Node.js runtime:
"buddy": {
"server": {
"port": 8000,
"file": "./index.js",
"env": {
"DEBUG": "*"
},
"flags": ["--inspect"]
}
}
When working with the default static file server, you can pass along custom headers:
"buddy": {
"server": {
"port": 8000,
"directory": "www",
"headers": {
"x-foo": "foo"
}
}
}
When executing the watch
command with the --serve
and --reload
flags, buddy will rely on the buddy-server plugin to launch a local development server, reloading any connected clients after re-builds. If the plugin is not already installed, buddy will automatically install it to your dev-dependencies
.
When running the deploy
command, the minified and gzipped file sizes will be automatically output to the terminal. If the minified size exceeds 250 kB, a warning will also be output:
building lib/react-browser.js to lib/react.js
[processed 169 files in 2.52s]
built and compressed src/lib/react.js
compressed size: 332 kB
gzipped size: 60.5 kB
warning the output file exceeds the recommended 250 kB size
Consider splitting into smaller bundles to help improve browser startup execution time
FAQs
A fast, simple build tool for web projects.
The npm package buddy receives a total of 16 weekly downloads. As such, buddy popularity was classified as not popular.
We found that buddy demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 2 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
PyPI now supports digital attestations, enhancing security and trust by allowing package maintainers to verify the authenticity of Python packages.
Security News
GitHub removed 27 malicious pull requests attempting to inject harmful code across multiple open source repositories, in another round of low-effort attacks.
Security News
RubyGems.org has added a new "maintainer" role that allows for publishing new versions of gems. This new permission type is aimed at improving security for gem owners and the service overall.