├── .github └── ISSUE_TEMPLATE │ └── bug_report.md ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── example ├── example.js ├── example.min.js ├── partOne.js ├── partThree.js └── partTwo.js ├── importer.js ├── jscomps.js ├── package-lock.json ├── package.json └── test └── jscomps.test.js /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Additional context** 27 | Add any other context about the problem here, such as: 28 | - npm version. 29 | - node.js version. 30 | - JSComps version. 31 | - OS. 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 12 4 | before_install: 5 | - npm i -g snyk 6 | install: 7 | - npm i 8 | script: 9 | - snyk test 10 | - npm run test 11 | after_success: 12 | - snyk monitor -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Hamza Alalach 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

JSComps

2 |

3 | JSComps is a lightweight CLI solution to enable components support on Vanilla JS. 4 |

5 | 6 |
7 | 8 | 9 | [![Build Status](https://travis-ci.org/hamzaalalach/jscomps.svg?branch=master)](https://travis-ci.org/hamzaalalach/jscomps) ![NPM](https://img.shields.io/npm/l/jscomps) 10 | ![npm](https://img.shields.io/npm/v/jscomps) 11 | ![GitHub last commit](https://img.shields.io/github/last-commit/hamzaalalach/jscomps) 12 | 13 | 14 | ## Description 15 | JSComps helps you chunk Vanilla JS into small and scalable components. It takes a folder as input which works as the components container, it detects changes in it, if any made it'll automatically import all the parts into the minified output file. By default, the imports should be contained in the provided folder under the same name, and the output looks like this: providedfoldername.min.js. [See examples below.](#examples)

16 | 17 | 18 | ## Install 19 | 20 | ```bash 21 | npm install jscomps -g 22 | ``` 23 | 24 | ```bash 25 | yarn add jscomps 26 | ``` 27 |
28 | 29 | ## Recent changes 30 | - Outputting result code strange behavior fixed. 31 | - Performence improved. 32 | 33 |
34 | 35 | ## Usage 36 | jscomps [options] 37 | 38 | Please make sure it's installed globally.

39 | 40 | 41 | ## Options 42 | 43 | ```text 44 | 45 | -f Component folder. [string] [required] 46 | -w Watch a directory for changes. [boolean] [default: true] 47 | -o Custom output file path. [string] 48 | -i Custom input file path. [string] 49 | -m Minify and compress output file. [boolean] [default: true] 50 | --iife Wrap output code in an IIFE. [boolean] [default: false] 51 | -h, --help Show help. [boolean] 52 | -v, --version Show version number. [boolean] 53 | 54 | ``` 55 |

56 | ## Examples 57 | Consider we are in the following folder: 58 | ``` 59 | js/ 60 | └── dashboard/ 61 | ├── dashboard.js 62 | ├── header.js 63 | ├── nav.js 64 | └── sideBar.js 65 | 66 | ``` 67 | Each of the files has the following content: 68 | - header.js: 69 | ```javascript 70 | console.log("Header script is running..."); 71 | ``` 72 | - nav.js: 73 | ```javascript 74 | console.log("Nav script is running..."); 75 | ``` 76 | - sideBar.js: 77 | ```javascript 78 | console.log("Sidebar script is running..."); 79 | ``` 80 | 81 | The import file, which is dashboard.js by default, should look like this: 82 | ```javascript 83 | import "header"; 84 | import "nav"; 85 | import "sideBar"; 86 | ``` 87 | We can now watch for changes and automatically concatenate our component pieces running the following command: 88 | ```bash 89 | jscomps -f js/dashboard -m false 90 | ``` 91 | Making any change to the 3 pieces of our component will trigger the output file to be generated ```dashboard.min.js``` in the dashboard folder, containing the following code: 92 | 93 | ```javascript 94 | console.log("Header script is running..."); 95 | console.log("Nav script is running..."); 96 | console.log("Sidebar script is running..."); 97 | ``` 98 | 99 | To compress the output code simply remove the -m parameter as it is by default set to true: 100 | ```bash 101 | jscomps -f js/dashboard 102 | ``` 103 | We can stop watching for changes and use the command only once: 104 | ```bash 105 | jscomps -f js/dashboard -w false 106 | ``` 107 | 108 | To provide a custom input and/or output file path, use the -o and -i commands: 109 | ```bash 110 | jscomps -f js/dashboard -i js/dashboard/index.js -o js/dashboard.js 111 | ``` 112 | 113 | If you like to wrap the output code in an IIFE, use --iife command: 114 | ```bash 115 | jscomps -f js/dashboard --iife true 116 | ``` 117 | 118 | The best way to use JSComps is to add it to your package.json file like this: 119 | ```json 120 | "scripts": { 121 | "jscomps:dashboard": "jscomps -f public/js/dashboard" 122 | } 123 | ``` 124 | Then you simply run: ` npm run jscomps:dashboard `

125 | 126 | ## Versioning 127 | 128 | We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/hamzaalalach/jscomps/tags).

129 | 130 | ## License 131 | 132 | This project is licensed under the MIT License. See the [LICENSE.md](LICENSE) file for details. 133 | -------------------------------------------------------------------------------- /example/example.js: -------------------------------------------------------------------------------- 1 | import 'partOne'; 2 | import 'partTwo'; 3 | import 'partThree'; 4 | -------------------------------------------------------------------------------- /example/example.min.js: -------------------------------------------------------------------------------- 1 | console.log("Hi there! I'm part one of your beautiful component."),console.log("Hello again! I'm part two of your beautiful component."),console.log("It's me! I'm part three of your beautiful component."); -------------------------------------------------------------------------------- /example/partOne.js: -------------------------------------------------------------------------------- 1 | console.log("Hi there! I'm part one of your beautiful component."); -------------------------------------------------------------------------------- /example/partThree.js: -------------------------------------------------------------------------------- 1 | console.log("It's me! I'm part three of your beautiful component."); -------------------------------------------------------------------------------- /example/partTwo.js: -------------------------------------------------------------------------------- 1 | console.log("Hello again! I'm part two of your beautiful component."); -------------------------------------------------------------------------------- /importer.js: -------------------------------------------------------------------------------- 1 | module.exports = (function() { 2 | const path = require('path'); 3 | const fs = require('fs'); 4 | 5 | return function(importFile) { 6 | return new Promise((resolve, reject) => { 7 | if (!fs.existsSync(importFile)) { 8 | reject('Not a valid file!'); 9 | } else { 10 | const data = fs.readFileSync(importFile); 11 | let imports = []; 12 | 13 | for (const line of data.toString().trim().split('\n')) { 14 | if (!/^import\s["']([a-zA-Z_][a-zA-Z0-9_-]+)["'];?/.test(line)) { 15 | reject('Imports format error!'); 16 | } else { 17 | imports.push(RegExp.$1); 18 | } 19 | } 20 | let result = ''; 21 | for (const elem of imports) { 22 | result += fs.readFileSync(path.resolve(path.dirname(importFile) + '/' + elem) + '.js') + '\n'; 23 | } 24 | resolve(result); 25 | } 26 | }); 27 | }; 28 | })(); 29 | -------------------------------------------------------------------------------- /jscomps.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require('fs'), 4 | uglifyjs = require('uglify-js'), 5 | path = require('path'), 6 | chalk = require('chalk'), 7 | load = require('./importer'), 8 | argv = require('yargs') 9 | .usage('Usage: $0 [options]') 10 | .options({ 11 | f: { 12 | describe: 'Component folder.', 13 | type: 'string', 14 | demandeOption: true, 15 | nargs: 1 16 | }, 17 | w: { 18 | describe: 'Watch a directory for changes.', 19 | default: true, 20 | boolean: true, 21 | demandeOption: false, 22 | nargs: 1 23 | }, 24 | o: { 25 | describe: 'Custom output file path.', 26 | type: 'string', 27 | demandeOption: false, 28 | nargs: 1 29 | }, 30 | i: { 31 | describe: 'Custom input file path.', 32 | type: 'string', 33 | demandeOption: false, 34 | nargs: 1 35 | }, 36 | m: { 37 | describe: 'Minify and compress output file.', 38 | default: true, 39 | boolean: true, 40 | demandeOption: false, 41 | nargs: 1 42 | }, 43 | iife: { 44 | describe: 'Wrap the result in an IIFE.', 45 | default: false, 46 | boolean: true, 47 | demandeOption: false, 48 | nargs: 1 49 | } 50 | }) 51 | .demandOption([ 'f' ], 'Please provide an folder file.') 52 | .example('$0 -f example') 53 | .example('$0 -f example -m false -w false -o exampleoutput.js') 54 | .alias('h', 'help') 55 | .alias('v', 'version') 56 | .help().argv; 57 | 58 | let fsWait = false, 59 | inputPath, 60 | outputPath; 61 | 62 | const setup = folderName => { 63 | return new Promise(function(resolve, reject) { 64 | fs.stat(argv.f, (err, stats) => { 65 | if (err || !stats.isDirectory()) { 66 | reject(); 67 | } else { 68 | folderName = path.basename(argv.f); 69 | if (argv.f[argv.f.length - 1] == '/') { 70 | argv.f = argv.f.substr(0, argv.f.length - 1); 71 | } 72 | if (argv.i) { 73 | inputPath = argv.i; 74 | } else { 75 | inputPath = argv.f + '/' + folderName + '.js'; 76 | } 77 | if (argv.o) { 78 | outputPath = argv.o; 79 | } else { 80 | outputPath = argv.f + '/' + folderName + '.min.js'; 81 | } 82 | resolve(); 83 | } 84 | }); 85 | }); 86 | }; 87 | 88 | const startAndWatch = folderPath => { 89 | fs.watch(folderPath, function(event, filename) { 90 | if (event === 'change') { 91 | if (fsWait) return; 92 | fsWait = setTimeout(() => { 93 | fsWait = false; 94 | }, 1000); 95 | } 96 | setTimeout(() => { 97 | start(filename); 98 | }, 500); 99 | }); 100 | }; 101 | 102 | const start = filename => { 103 | load(inputPath) 104 | .then(importedCode => { 105 | let result; 106 | if (argv.m) { 107 | minifyOutput = uglifyjs.minify(importedCode, { compress: true }); 108 | result = minifyOutput.code; 109 | if (minifyOutput.error) { 110 | console.log(minifyOutput.error); 111 | console.log(chalk.red('Minification error detected, waiting for changes...')); 112 | return; 113 | } 114 | } else { 115 | result = importedCode; 116 | } 117 | if (argv.iife) { 118 | let iifeStart = '(function() {'; 119 | if (!argv.m) { 120 | iifeStart += '\n'; 121 | } 122 | result = iifeStart + result + '})();'; 123 | } 124 | fs.writeFile(outputPath, result, function(err) { 125 | if (err) throw err; 126 | filename 127 | ? console.log(filename + ' changed: ' + chalk.green('Files imported.')) 128 | : console.log('Files imported.'); 129 | }); 130 | }) 131 | .catch(error => { 132 | console.log(chalk.red(error)); 133 | }); 134 | }; 135 | 136 | setup() 137 | .then(() => { 138 | if (argv.w) { 139 | console.log(chalk.green('Watching folder: ' + chalk.white(argv.f))); 140 | console.log(chalk.green('Waiting for changes...')); 141 | startAndWatch(argv.f); 142 | } else { 143 | start(); 144 | } 145 | }) 146 | .catch(() => console.log(chalk.red('Not a valid watch folder.'))); 147 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jscomps", 3 | "version": "1.1.1", 4 | "description": "Chunk ES5 JavaScript into small easily scalable components", 5 | "main": "jscomps.js", 6 | "scripts": { 7 | "test": "jest" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/hamzaalalach/jscomps.git" 12 | }, 13 | "keywords": [ 14 | "ES5", 15 | "JavaScript", 16 | "components", 17 | "cli" 18 | ], 19 | "author": "Hamza Alalach", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/hamzaalalach/jscomps/issues" 23 | }, 24 | "bin": { 25 | "jscomps": "./jscomps.js" 26 | }, 27 | "homepage": "https://github.com/hamzaalalach/jscomps#readme", 28 | "dependencies": { 29 | "chalk": "^3.0.0", 30 | "uglify-js": "^3.7.0", 31 | "yargs": "^15.4.1" 32 | }, 33 | "devDependencies": { 34 | "jest": "^24.9.0" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /test/jscomps.test.js: -------------------------------------------------------------------------------- 1 | const util = require('util'); 2 | const exec = util.promisify(require('child_process').exec); 3 | 4 | test('Should output error if no watch folder provided.', () => { 5 | return runJSComps('node jscomps').then().catch((data) => { 6 | expect(data.stderr).not.toBe(''); 7 | }); 8 | }); 9 | 10 | test('Should output error if watch folder doesn\'t exist.', () => { 11 | return runJSComps('node jscomps -f something').then((data) => { 12 | expect(data.stdout).toBe('Not a valid watch folder.\n'); 13 | }).catch(); 14 | }); 15 | 16 | test('Should import files if watch folder is provided.', () => { 17 | return runJSComps('node jscomps -f example -w false').then((data) => { 18 | expect(data.stdout).toBe('Files imported.\n'); 19 | }).catch(); 20 | }); 21 | 22 | const runJSComps = async (command) => { 23 | return await exec(command); 24 | } --------------------------------------------------------------------------------