diff --git a/.travis.yml b/.travis.yml index a3986c5..8c12d19 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,7 @@ language: node_js node_js: - 8 + - 10 git: depth: 1 branches: diff --git a/README.md b/README.md index 898be3f..78a1fd5 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![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/) -A minimalistic CommonJS bundler +A minimalistic CommonJS bundler. - - - @@ -52,7 +52,7 @@ asbundle(sourceFileName); ### Features - * extremely lightweight, based on [esprima](http://esprima.org) for performance and reliability + * extremely lightweight, based on [babylon](https://github.com/babel/babylon) for performance and reliability * it uses _ascjs_ to automatically transform, when needed, ES2015+ modules into CommonJS code * understands both relative files and installed packages too (based on `require.resolve(...)`) * reproduces a modern and minimalistic CommonJS environments ideal for browsers diff --git a/bin.js b/bin.js index 16ffd44..b8dd29e 100755 --- a/bin.js +++ b/bin.js @@ -1,8 +1,9 @@ #!/usr/bin/env node const asbundle = require('./index.js'); +const argv = process.argv.filter(arg => !/^-/.test(arg)); -let source = process.argv[2]; +let source = argv[2]; if (!source) { const info = require('./package.json'); console.log(` @@ -21,14 +22,16 @@ ${' '.repeat(info.description.length) } else { const fs = require('fs'); const path = require('path'); - let dest = process.argv[3]; + let dest = argv.filter(arg => !/^-/.test(arg))[3]; source = path.resolve(process.cwd(), source); + if (dest) { + dest = path.resolve(process.cwd(), dest) + } fs.stat(source, (err, stat) => { if (err) throw err; if (!stat.isFile()) throw `unknown file ${source}`; const result = asbundle(source); if (dest) { - dest = path.resolve(process.cwd(), dest); fs.writeFileSync(dest, result); } else process.stdout.write(result); diff --git a/index.js b/index.js index 7b86eb5..ef383dc 100644 --- a/index.js +++ b/index.js @@ -1,16 +1,43 @@ const ascjs = require('ascjs'); -const esprima = require('esprima'); +const parser = require('babylon'); const defaultOptions = { sourceType: 'module', - jsx: true, - range: true, - tolerant: true + plugins: [ + 'estree', + 'jsx', + 'flow', + 'typescript', + 'doExpressions', + 'objectRestSpread', + 'decorators', + 'decorators2', + 'classProperties', + 'classPrivateProperties', + 'classPrivateMethods', + 'exportExtensions', + 'asyncGenerators', + 'functionBind', + 'functionSent', + 'dynamicImport', + 'numericSeparator', + 'optionalChaining', + 'importMeta', + 'bigInt', + 'optionalCatchBinding', + 'throwExpressions', + 'pipelineOperator', + 'nullishCoalescingOperator' + ] }; -const execSync = require('child_process').execSync; const fs = require('fs'); const path = require('path'); +const CGJS = process.argv.some(arg => arg === '--cgjs'); +const IGNORE = process.argv.filter(arg => /^--ignore=/.test(arg)) + .map(arr => arr.slice(9).split(','))[0] || []; +const IMPORT = 'require.I'; +const EXPORT = 'require.E(exports)'; const bundle = (main, options) => { let base = path.dirname(main); main = path.resolve(base, main); @@ -28,7 +55,7 @@ const bundle = (main, options) => { cache, modules ); - return ` + let output = ` (function (cache, modules) { function require(i) { return cache[i] || get(i); } function get(i) { @@ -44,48 +71,95 @@ ${code} } `.trim()).join(',')}])); `.trim(); + if (CGJS) { + output = output.replace( + 'cache[i] || get(i)', + 'typeof i === "string" ? window.require(i) : (cache[i] || get(i))' + ); + } + if (output.includes('require.E(exports)')) { + output = output.replace( + 'var main =', + 'require.E = function (exports) { return Object.defineProperty(exports, \'__esModule\', {value: true}); };\n var main =' + ); + } + if (output.includes(IMPORT)) { + output = output.replace( + 'var main =', + IMPORT + ' = function (m) { return m.__esModule ? m.default : m; };\n var main =' + ).replace( + 'var main = require(0);\n return main.__esModule ? main.default : main;', + 'return require.I(require(0));' + ); + } + return output; }; const parse = (options, base, file, cache, modules) => { const out = []; const chunks = []; let code = fs.readFileSync(file).toString(); - if (/^(?:import|export)\s+/m.test(code)) code = ascjs(code); + if (/^(?:import|export)\s+/m.test(code)) code = ascjs(code, {IMPORT, EXPORT}); const addChunk = (module, name) => { const i = cache.indexOf(name); chunks.push({ - start: module.range[0], - end: module.range[1], + start: module.start, + end: module.end, value: i < 0 ? (cache.push(name) - 1) : i }); if (i < 0) { modules[cache.length - 1] = parse(options, path.dirname(name), name, cache, modules); } }; - esprima.parse( - code, - options, - item => { - if (item.type === 'CallExpression' && item.callee.name === 'require') { - const module = item.arguments[0]; + const findRequire = item => { + if (item.type === 'CallExpression' && item.callee.name === 'require') { + const module = item.arguments[0]; + if (IGNORE.indexOf(module.value) < 0) { if (/^[./]/.test(module.value)) { - let name = path.resolve(base, module.value); - addChunk(module, /\.js$/.test(name) ? name : (name + '.js')); - } else { - let name = execSync( - `node -e 'console.log(require.resolve(${ - JSON.stringify(module.value) - }))'`, - {cwd: base} - ).toString().trim(); - if (name === module.value) { - throw `unable to find "${name}" via file://${file}\n`; + let name + try { + name = require.resolve(path.resolve(base, module.value)); + } catch (e) { + process.stderr.write('Error resolving ' + module.value + ' from ' + base + ': ' + e.message) + throw e } - addChunk(module, name); + if (/\.m?js$/.test(name)) addChunk(module, name); + else { + const i = cache.indexOf(name); + if (i < 0) cache.push(name); + addChunk(module, name); + if (i < 0) { + modules[cache.length - 1] = `module.exports = ${JSON.stringify(require(name))};`; + } + } + } else if (!/^[A-Z]/.test(module.value)) { + process.chdir(base); + const name = require.resolve(module.value); + if (name !== module.value) addChunk(module, name); + else if (!CGJS) throw `unable to find "${name}" via file://${file}\n`; + } + } else { + chunks.push({ + start: item.callee.start, + end: item.callee.end, + value: '' + }, + { + start: module.start, + end: module.end, + value: 'null' + }); + } + } else { + for (let key in item) { + if (typeof item[key] === 'object') { + findRequire(item[key] || {}); } } } - ); + }; + const parsed = parser.parse(code, options); + parsed.program.body.forEach(findRequire); const length = chunks.length; let c = 0; for (let i = 0; i < length; i++) { diff --git a/package.json b/package.json index f8cf2a5..8cae6d4 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,11 @@ { "name": "asbundle", - "version": "1.0.1", + "version": "2.7.0", "description": "A minimalistic CommonJS bundler", "bin": "bin.js", "main": "index.js", "scripts": { - "test": "if [ \"$(node bin.js test/main.js)\" != \"$(cat test/out.js)\" ]; then exit 1; fi" + "test": "if [ \"$(node bin.js test/main.js --ignore=m1,m2)\" != \"$(cat test/out.js)\" ]; then exit 1; fi" }, "repository": { "type": "git", @@ -30,7 +30,7 @@ }, "homepage": "https://github.com/WebReflection/asbundle#readme", "dependencies": { - "ascjs": "1.0.1", - "esprima": "^4.0.0" + "ascjs": "^4.0.1", + "babylon": "^6.18.0" } } diff --git a/test/cgjs.js b/test/cgjs.js new file mode 100644 index 0000000..7c61c57 --- /dev/null +++ b/test/cgjs.js @@ -0,0 +1,4 @@ +const Gtk = require('Gtk'); +const util = require('util'); + +console.log(util.inspect(Gtk.init)); \ No newline at end of file diff --git a/test/json.js b/test/json.js new file mode 100644 index 0000000..4baebbc --- /dev/null +++ b/test/json.js @@ -0,0 +1,3 @@ +const json = require('../package.json'); + +console.log(json); \ No newline at end of file diff --git a/test/main.js b/test/main.js index 9023225..31a4593 100644 --- a/test/main.js +++ b/test/main.js @@ -3,4 +3,6 @@ const val = 123; export default function test() { console.log('asbundle'); }; -export {func, val}; \ No newline at end of file +export {func, val}; +require('m1'); +require('m2'); \ No newline at end of file diff --git a/test/out.js b/test/out.js index d1236bd..fbbbc4e 100644 --- a/test/out.js +++ b/test/out.js @@ -5,27 +5,30 @@ modules[i].call(exports, window, require, module, exports); return (cache[i] = module.exports); } - var main = require(0); - return main.__esModule ? main.default : main; + require.E = function (exports) { return Object.defineProperty(exports, '__esModule', {value: true}); }; + require.I = function (m) { return m.__esModule ? m.default : m; }; + return require.I(require(0)); }([],[function (global, require, module, exports) { // main.js 'use strict'; -const func = (m => m.__esModule ? m.default : m)(require(1)); +const func = require.I(require(1)); const {a, b} = require(1); const val = 123; function test() { console.log('asbundle'); } -Object.defineProperty(exports, '__esModule', {value: true}).default = test; +require.E(exports).default = test; exports.func = func; exports.val = val; +(null); +(null); },function (global, require, module, exports) { // module.js 'use strict'; const a = 1, b = 2; exports.a = a; exports.b = b; -Object.defineProperty(exports, '__esModule', {value: true}).default = function () { +require.E(exports).default = function () { console.log('module'); }; }])); \ No newline at end of file