├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE.txt ├── README.md ├── bin.js ├── index.js ├── module-structure.png ├── package.json └── test ├── cgjs.js ├── json.js ├── main.js ├── module.js └── out.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | package-lock.json -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | test/* 3 | .gitignore 4 | .travis.yml 5 | module-structure.png 6 | package-lock.json -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 8 4 | - 10 5 | git: 6 | depth: 1 7 | branches: 8 | only: 9 | - master 10 | - /^greenkeeper/.*$/ 11 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2017, Andrea Giammarchi, @WebReflection 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 10 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 11 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 12 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 13 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE 14 | OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 15 | PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # asbundle 2 | 3 | [![donate](https://img.shields.io/badge/$-donate-ff69b4.svg?maxAge=2592000&style=flat)](https://github.com/WebReflection/donate) [![License: ISC](https://img.shields.io/badge/License-ISC-yellow.svg)](https://opensource.org/licenses/ISC) [![Build Status](https://travis-ci.org/WebReflection/asbundle.svg?branch=master)](https://travis-ci.org/WebReflection/asbundle) [![Greenkeeper badge](https://badges.greenkeeper.io/WebReflection/asbundle.svg)](https://greenkeeper.io/) 4 | 5 | A minimalistic CommonJS bundler. 6 | 7 | - - - 8 | 9 | This module is a perfect [ascjs](https://github.com/WebReflection/ascjs) companion to create CommonJS bundles. 10 | 11 | Passing a single ESM/CJS source file as path name, it will produce a lightweight, optimized, and minifier friendly bundle, 12 | to consume right away without needing global `require` or runtime discovered CommonJS dependencies. 13 | 14 | ### Goals 15 | * be as simple as possible, but not simpler 16 | * make creation of small modules written in ESM a no brainer 17 | * enable `.js` files as ESM everywhere, following a simple folder convention 18 | * produce files compatible with most common bundlers and tools (Babel, Webpack, UglifyJS, etc) 19 | 20 | **Example** of a basic module based on _ascjs_ and _asbundle_. 21 | 22 | 23 | 24 | ### Non-Goals 25 | * replace Babel, Webpack, Rollup, or any other tool. Let them do complicated things when needed 26 | * transpile anything else than ESM import/export declarations 27 | 28 | ## How to 29 | 30 | You can use _asbundle_ as binary utility or as module. 31 | 32 | ```sh 33 | npm install -g asbundle 34 | 35 | # to see what you can do 36 | asbundle --help 37 | 38 | ``` 39 | 40 | As executable, you can use _asbundle_ to output, or save, a bundle entry point. 41 | ```sh 42 | asbundle source-file.js # writes bundle contents to STDOUT 43 | asbundle source-file.js bundle.js # outputs to bundle.js 44 | ``` 45 | 46 | As module, you can require it and use it to obtain a bundle string. 47 | ```js 48 | const asbundle = require('asbundle'); 49 | 50 | asbundle(sourceFileName); 51 | ``` 52 | 53 | ### Features 54 | 55 | * extremely lightweight, based on [babylon](https://github.com/babel/babylon) for performance and reliability 56 | * it uses _ascjs_ to automatically transform, when needed, ES2015+ modules into CommonJS code 57 | * understands both relative files and installed packages too (based on `require.resolve(...)`) 58 | * reproduces a modern and minimalistic CommonJS environments ideal for browsers 59 | * compatible with [Babel](http://babeljs.io) `__esModule` and `.default` convention 60 | 61 | ### Constrains 62 | 63 | * same constrains of [ascjs](https://github.com/WebReflection/ascjs#constrains) 64 | * Node core modules are not brought to the bundle, if a module cannot be resolved as file name it throws 65 | 66 | ### Example 67 | This module can transform `main.js` entry file via `asbundle main.js out.js`: 68 | ```js 69 | // main.js 70 | import func, {a, b} from './module.js'; 71 | const val = 123; 72 | export default function test() { 73 | console.log('asbundle'); 74 | }; 75 | export {func, val}; 76 | 77 | // module.js 78 | export const a = 1, b = 2; 79 | export default function () { 80 | console.log('module'); 81 | }; 82 | ``` 83 | into the following bundle: 84 | ```js 85 | // out.js => 266 bytes minified & gzipped 86 | (function (cache, modules) { 87 | function require(i) { return cache[i] || get(i); } 88 | function get(i) { 89 | var exports = {}, module = {exports: exports}; 90 | modules[i].call(exports, window, require, module, exports); 91 | return (cache[i] = module.exports); 92 | } 93 | var main = require(0); 94 | return main.__esModule ? main.default : main; 95 | }([],[function (global, require, module, exports) { 96 | // main.js 97 | 'use strict'; 98 | const func = (m => m.__esModule ? m.default : m)(require(1)); 99 | const {a, b} = require(1); 100 | const val = 123; 101 | function test() { 102 | console.log('asbundle'); 103 | } 104 | Object.defineProperty(exports, '__esModule', {value: true}).default = test; 105 | exports.func = func; 106 | exports.val = val; 107 | },function (global, require, module, exports) { 108 | // module.js 109 | 'use strict'; 110 | const a = 1, b = 2; 111 | exports.a = a; 112 | exports.b = b; 113 | Object.defineProperty(exports, '__esModule', {value: true}).default = function () { 114 | console.log('module'); 115 | }; 116 | }])); 117 | ``` 118 | 119 | The main module is returned and executed as default entry so it becomes easy to publish as global variable for Web purposes too. 120 | 121 | Add a `const myModule = ` prefix to the bundled code and use it right away. 122 | -------------------------------------------------------------------------------- /bin.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const asbundle = require('./index.js'); 4 | const argv = process.argv.filter(arg => !/^-/.test(arg)); 5 | 6 | let source = argv[2]; 7 | if (!source) { 8 | const info = require('./package.json'); 9 | console.log(` 10 | \x1B[1masbundle\x1B[0m v${info.version} 11 | ${'-'.repeat(info.description.length)} 12 | ${info.description} 13 | ${'-'.repeat(info.description.length)} 14 | 15 | asbundle sourceFile 16 | asbundle sourceFile destFile 17 | 18 | ${'-'.repeat(info.description.length)} 19 | ${' '.repeat(info.description.length) 20 | .slice(0, -(3 + info.author.length))}by ${info.author} 21 | `); 22 | } else { 23 | const fs = require('fs'); 24 | const path = require('path'); 25 | let dest = argv.filter(arg => !/^-/.test(arg))[3]; 26 | source = path.resolve(process.cwd(), source); 27 | if (dest) { 28 | dest = path.resolve(process.cwd(), dest) 29 | } 30 | fs.stat(source, (err, stat) => { 31 | if (err) throw err; 32 | if (!stat.isFile()) throw `unknown file ${source}`; 33 | const result = asbundle(source); 34 | if (dest) { 35 | fs.writeFileSync(dest, result); 36 | } 37 | else process.stdout.write(result); 38 | }); 39 | } 40 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const ascjs = require('ascjs'); 2 | const parser = require('babylon'); 3 | const defaultOptions = { 4 | sourceType: 'module', 5 | plugins: [ 6 | 'estree', 7 | 'jsx', 8 | 'flow', 9 | 'typescript', 10 | 'doExpressions', 11 | 'objectRestSpread', 12 | 'decorators', 13 | 'decorators2', 14 | 'classProperties', 15 | 'classPrivateProperties', 16 | 'classPrivateMethods', 17 | 'exportExtensions', 18 | 'asyncGenerators', 19 | 'functionBind', 20 | 'functionSent', 21 | 'dynamicImport', 22 | 'numericSeparator', 23 | 'optionalChaining', 24 | 'importMeta', 25 | 'bigInt', 26 | 'optionalCatchBinding', 27 | 'throwExpressions', 28 | 'pipelineOperator', 29 | 'nullishCoalescingOperator' 30 | ] 31 | }; 32 | 33 | const fs = require('fs'); 34 | const path = require('path'); 35 | const CGJS = process.argv.some(arg => arg === '--cgjs'); 36 | const IGNORE = process.argv.filter(arg => /^--ignore=/.test(arg)) 37 | .map(arr => arr.slice(9).split(','))[0] || []; 38 | 39 | const IMPORT = 'require.I'; 40 | const EXPORT = 'require.E(exports)'; 41 | const bundle = (main, options) => { 42 | let base = path.dirname(main); 43 | main = path.resolve(base, main); 44 | base = path.dirname(main); 45 | const cache = [main]; 46 | const modules = []; 47 | modules[0] = parse( 48 | Object.assign( 49 | {}, 50 | defaultOptions, 51 | options 52 | ), 53 | base, 54 | main, 55 | cache, 56 | modules 57 | ); 58 | let output = ` 59 | (function (cache, modules) { 60 | function require(i) { return cache[i] || get(i); } 61 | function get(i) { 62 | var exports = {}, module = {exports: exports}; 63 | modules[i].call(exports, window, require, module, exports); 64 | return (cache[i] = module.exports); 65 | } 66 | var main = require(0); 67 | return main.__esModule ? main.default : main; 68 | }([],[${modules.map((code, i) => `function (global, require, module, exports) { 69 | // ${path.relative(base, cache[i])} 70 | ${code} 71 | } 72 | `.trim()).join(',')}])); 73 | `.trim(); 74 | if (CGJS) { 75 | output = output.replace( 76 | 'cache[i] || get(i)', 77 | 'typeof i === "string" ? window.require(i) : (cache[i] || get(i))' 78 | ); 79 | } 80 | if (output.includes('require.E(exports)')) { 81 | output = output.replace( 82 | 'var main =', 83 | 'require.E = function (exports) { return Object.defineProperty(exports, \'__esModule\', {value: true}); };\n var main =' 84 | ); 85 | } 86 | if (output.includes(IMPORT)) { 87 | output = output.replace( 88 | 'var main =', 89 | IMPORT + ' = function (m) { return m.__esModule ? m.default : m; };\n var main =' 90 | ).replace( 91 | 'var main = require(0);\n return main.__esModule ? main.default : main;', 92 | 'return require.I(require(0));' 93 | ); 94 | } 95 | return output; 96 | }; 97 | 98 | const parse = (options, base, file, cache, modules) => { 99 | const out = []; 100 | const chunks = []; 101 | let code = fs.readFileSync(file).toString(); 102 | if (/^(?:import|export)\s+/m.test(code)) code = ascjs(code, {IMPORT, EXPORT}); 103 | const addChunk = (module, name) => { 104 | const i = cache.indexOf(name); 105 | chunks.push({ 106 | start: module.start, 107 | end: module.end, 108 | value: i < 0 ? (cache.push(name) - 1) : i 109 | }); 110 | if (i < 0) { 111 | modules[cache.length - 1] = parse(options, path.dirname(name), name, cache, modules); 112 | } 113 | }; 114 | const findRequire = item => { 115 | if (item.type === 'CallExpression' && item.callee.name === 'require') { 116 | const module = item.arguments[0]; 117 | if (IGNORE.indexOf(module.value) < 0) { 118 | if (/^[./]/.test(module.value)) { 119 | let name 120 | try { 121 | name = require.resolve(path.resolve(base, module.value)); 122 | } catch (e) { 123 | process.stderr.write('Error resolving ' + module.value + ' from ' + base + ': ' + e.message) 124 | throw e 125 | } 126 | if (/\.m?js$/.test(name)) addChunk(module, name); 127 | else { 128 | const i = cache.indexOf(name); 129 | if (i < 0) cache.push(name); 130 | addChunk(module, name); 131 | if (i < 0) { 132 | modules[cache.length - 1] = `module.exports = ${JSON.stringify(require(name))};`; 133 | } 134 | } 135 | } else if (!/^[A-Z]/.test(module.value)) { 136 | process.chdir(base); 137 | const name = require.resolve(module.value); 138 | if (name !== module.value) addChunk(module, name); 139 | else if (!CGJS) throw `unable to find "${name}" via file://${file}\n`; 140 | } 141 | } else { 142 | chunks.push({ 143 | start: item.callee.start, 144 | end: item.callee.end, 145 | value: '' 146 | }, 147 | { 148 | start: module.start, 149 | end: module.end, 150 | value: 'null' 151 | }); 152 | } 153 | } else { 154 | for (let key in item) { 155 | if (typeof item[key] === 'object') { 156 | findRequire(item[key] || {}); 157 | } 158 | } 159 | } 160 | }; 161 | const parsed = parser.parse(code, options); 162 | parsed.program.body.forEach(findRequire); 163 | const length = chunks.length; 164 | let c = 0; 165 | for (let i = 0; i < length; i++) { 166 | out.push( 167 | code.slice(c, chunks[i].start), 168 | chunks[i].value 169 | ); 170 | c = chunks[i].end; 171 | } 172 | out.push(length ? code.slice(c) : code); 173 | return out.join(''); 174 | }; 175 | 176 | module.exports = bundle; 177 | -------------------------------------------------------------------------------- /module-structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebReflection/asbundle/a17d9bdcd4a78a730ad67acbe3d96d66a752f76b/module-structure.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "asbundle", 3 | "version": "2.7.0", 4 | "description": "A minimalistic CommonJS bundler", 5 | "bin": "bin.js", 6 | "main": "index.js", 7 | "scripts": { 8 | "test": "if [ \"$(node bin.js test/main.js --ignore=m1,m2)\" != \"$(cat test/out.js)\" ]; then exit 1; fi" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/WebReflection/asbundle.git" 13 | }, 14 | "keywords": [ 15 | "esm", 16 | "cjs", 17 | "commonjs", 18 | "browserify", 19 | "alternative", 20 | "bundler", 21 | "simple", 22 | "basic", 23 | "browsers", 24 | "minimalistic" 25 | ], 26 | "author": "Andrea Giammarchi", 27 | "license": "ISC", 28 | "bugs": { 29 | "url": "https://github.com/WebReflection/asbundle/issues" 30 | }, 31 | "homepage": "https://github.com/WebReflection/asbundle#readme", 32 | "dependencies": { 33 | "ascjs": "^4.0.1", 34 | "babylon": "^6.18.0" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /test/cgjs.js: -------------------------------------------------------------------------------- 1 | const Gtk = require('Gtk'); 2 | const util = require('util'); 3 | 4 | console.log(util.inspect(Gtk.init)); -------------------------------------------------------------------------------- /test/json.js: -------------------------------------------------------------------------------- 1 | const json = require('../package.json'); 2 | 3 | console.log(json); -------------------------------------------------------------------------------- /test/main.js: -------------------------------------------------------------------------------- 1 | import func, {a, b} from './module.js'; 2 | const val = 123; 3 | export default function test() { 4 | console.log('asbundle'); 5 | }; 6 | export {func, val}; 7 | require('m1'); 8 | require('m2'); -------------------------------------------------------------------------------- /test/module.js: -------------------------------------------------------------------------------- 1 | export const a = 1, b = 2; 2 | export default function () { 3 | console.log('module'); 4 | }; -------------------------------------------------------------------------------- /test/out.js: -------------------------------------------------------------------------------- 1 | (function (cache, modules) { 2 | function require(i) { return cache[i] || get(i); } 3 | function get(i) { 4 | var exports = {}, module = {exports: exports}; 5 | modules[i].call(exports, window, require, module, exports); 6 | return (cache[i] = module.exports); 7 | } 8 | require.E = function (exports) { return Object.defineProperty(exports, '__esModule', {value: true}); }; 9 | require.I = function (m) { return m.__esModule ? m.default : m; }; 10 | return require.I(require(0)); 11 | }([],[function (global, require, module, exports) { 12 | // main.js 13 | 'use strict'; 14 | const func = require.I(require(1)); 15 | const {a, b} = require(1); 16 | const val = 123; 17 | function test() { 18 | console.log('asbundle'); 19 | } 20 | require.E(exports).default = test; 21 | exports.func = func; 22 | exports.val = val; 23 | (null); 24 | (null); 25 | },function (global, require, module, exports) { 26 | // module.js 27 | 'use strict'; 28 | const a = 1, b = 2; 29 | exports.a = a; 30 | exports.b = b; 31 | require.E(exports).default = function () { 32 | console.log('module'); 33 | }; 34 | }])); --------------------------------------------------------------------------------