Last active
May 14, 2018 00:57
-
-
Save gtzilla/544a84a214acdf78058f3584aa72a71b to your computer and use it in GitHub Desktop.
Minimal Underscore Template Build System, version 0.0.1. Not a complex, configurable or complete build system for underscore.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict'; | |
/** | |
ATTENTION: This is moved to https://github.com/gtzilla/minimal_.js | |
minimal_.js | |
minimal_ | |
Minimal Underscore Template Build System, version 0.0.1 | |
Not a complex, configurable or complete build system for underscore.js | |
Transforms HTML templates, written for underscore.js template library, into JavaScript methods | |
and writes to output file, such as js-dist/templates.js. | |
Works with Chrome Extensions limited environment. Originally written to support | |
JavaScript templating in Chrome Extensions and Browser Extensions | |
requires: underscore.js, node v10 | |
Much like other configurable build systems, this | |
script concatenates files together. To improve speed, | |
this scrpit keeps an intermediary step folder, processing HTML to JS into a js-dist-separate folder | |
then watching this intermediary folder for changes, via fs.watch, for combining to templates.js | |
ASCI Visualization | |
.html --> .js -> | | |
.html --> .js -> | (*.js) ---> templates.js | |
.html --> .js -> | | |
This has a few benefits, some drawbacks. | |
Benefit: | |
Faster compile time for templates.js, don't have to recompile all files, just combine | |
can link directly to final file during dev. for chrome exts this is helpful bc no server | |
can link directly to individual "compiled" files (html->js) | |
Drawback: | |
Not a "standardize" build system | |
Extending requires modifications to original | |
documentation is very thin | |
Usage, Building: | |
// debug prints values from qlog | |
node minimal_.js --debug | |
node minimal_.js --watch relative/path/from/this_script/folder | |
node minimal_.js --distro relative/path/for/output/templates.js | |
node minimal_.js --distro-separate relative/path/single/files | |
In prod | |
node minimal_.js --only-build | |
Using with Yarn, Building: | |
yarn run watch | |
Can mix command line arguments with yarn commands | |
yarn watch --debug | |
Package.json: | |
{ | |
"scripts": { | |
"watch":"node scripts/minimal_.js --watch file/path/here --distro output/path/here" | |
} | |
} | |
Usage, Implementation: | |
Link to the final, js-dist/templates.js, from HTML template. | |
All files are translated to | |
the appropriately named `templates.method`. For example, the HTML template: `my_first_template.html` | |
would be available as templates.my_first_template and used like `templates.my_first_template({...}) | |
This script setup avoids underscore 'with' template scoping and instead | |
declares {'variable':'data'} | |
making all template variables descend from a data [Object Object] | |
Other Thoughts: | |
Uses some conventions from es2015 and later, such as let, | |
const arrow anonymous function declaration (syntax sugar) | |
Skips promises and async/await to avoid | |
needing a build system for the build system. too meta | |
Assumed Directory Structure: | |
(project root) | |
/ | |
- yarn.lock | |
- package.json | |
| | |
--> /scripts/minimal_.js | |
| | |
--> /js-templates/*.html | |
| | |
--> /js-dist/templates.js | |
| | |
--> /js-dist-separate/*.js | |
*/ | |
var program = require('commander'); | |
// PARSE CLI arguments into structure | |
program | |
.version('0.0.1') | |
.option('-w, --watch [value]', 'Watch this directory for changes to HTML') | |
.option('-d, --distro [value]', 'Path to distro folder, templates.js') | |
.option('-s, --distro-separate [value]', 'Single JS templates folder') | |
.option('--debug', 'Debugging, default is false', 0) | |
.option('--silent', 'Quiet output. Forcibly Overrides debug to false.') | |
.option('--only-build', 'Do not watch, only build files. Overrides watch flag.') | |
.parse(process.argv); | |
const monitor_path_str = program.watch || "js-templates"; | |
const separate_compiled_path_str = program.distroSeparate || "js-dist-separate"; | |
const compiled_path_str = program.distro || "js-dist"; | |
let default_underscore_template_config = {variable:'data'}; | |
// external packages | |
const path = require('path'); | |
const fs = require("fs"); | |
const _ = require("underscore"); | |
// paths on filesystem | |
// IMPROVE: be smarter, if abs path don't join | |
// GLOBALS! | |
const monitor_path = path.join(__dirname, "../", monitor_path_str); | |
const separate_compiled_path = path.join(__dirname, "../", separate_compiled_path_str); | |
const compiled_path = path.join(__dirname, "../", compiled_path_str); | |
// Quick, Sane, Logs | |
function qlog() { | |
if(program.debug) { | |
console.log.apply(console, arguments); | |
} | |
} | |
function on_file_write(err) { | |
if(err) { | |
qlog("Error on write", err); | |
} | |
} | |
function js_file_filter(item) { | |
let ext_type = path.extname(item); | |
if(ext_type.indexOf("js") < 0) return false; | |
return true; | |
} | |
function html_file_filter(item) { | |
let ext_type = path.extname(item); | |
if(ext_type.indexOf("html") < 0) return false; | |
return true; | |
} | |
function read_accumulate(_accum, total_cnt, on_complete=null) { | |
let counter = total_cnt; | |
return (item) => { | |
let read_path = path.join(separate_compiled_path, "" + item); | |
fs.readFile(read_path, 'utf8', (err, contents) => { | |
if(err) { | |
qlog("Error", err); | |
return; | |
} | |
counter -= 1; | |
_accum.push(contents); | |
if(counter <= 0 && _.isFunction(on_complete)) { | |
on_complete(_accum); | |
} | |
}); | |
} | |
} | |
function accumulator_completed(_accumulated) { | |
qlog("accumulator_completed!", _accumulated.length); | |
let out_str = '/** Auto generated */\n\r;var templates={};\n' + _accumulated.join("\n"); | |
fs.writeFile(path.join(compiled_path, "./", "templates.js"), out_str, on_file_write); | |
} | |
function on_js_directory_read(err, filenames) { | |
if(err) return; | |
let accumulater = []; | |
let filtered = _.filter(filenames, js_file_filter); | |
_.each(filtered, read_accumulate(accumulater, filtered.length, accumulator_completed)); | |
} | |
function monitor_and_compile_combined_js(event_type, filename) { | |
return fs.readdir(separate_compiled_path, on_js_directory_read); | |
} | |
function convert_html_to_js(infile, outfile, variable_name) { | |
return function(err, contents) { | |
if(err) { | |
qlog("ERROR!", err); | |
return; | |
} | |
// the actual important part. turn HTML into JS | |
let blob = _.template(contents, default_underscore_template_config).source; | |
fs.writeFile(outfile, 'templates.' + variable_name + "=" + blob, on_file_write); | |
} | |
} | |
function assemble_filepath(filename) { | |
let data = {}; | |
data.filename = "" +filename; | |
data.ext = path.parse(data.filename).ext; | |
data.variable = data.filename.replace(data.ext, ''); | |
data.in = path.join(monitor_path, data.filename); | |
data.out = path.join(separate_compiled_path, data.variable + ".js"); | |
return data; | |
} | |
function monitor_files_and_compile(event_type, filename) { | |
let info = assemble_filepath(filename); | |
if(event_type === 'change') { | |
fs.readFile(info.in, 'utf8', convert_html_to_js(info.in, info.out, info.variable)); | |
} | |
} | |
// MONSTER! gross, tear apart to be smaller. | |
// remove internal anon funcs | |
function on_html_directory_read(err, filenames) { | |
let filtered = _.filter(filenames, html_file_filter); | |
let counter = filtered.length; | |
_.each(filtered, function(filename) { | |
let info = assemble_filepath(filename); | |
fs.readFile(info.in, 'utf8', (err, contents) => { | |
// the actual important part. turn HTML into JS | |
let blob = _.template(contents, default_underscore_template_config).source; | |
fs.writeFile(info.out, 'templates.' + info.variable + "=" + blob, () => { | |
counter -= 1; | |
if(counter <= 0) { | |
// now build the templates.js file, read single js files, accumulate and print | |
fs.readdir(separate_compiled_path, (err, filenames) => { | |
let filtered = _.filter(filenames, js_file_filter); | |
_.each(filtered, read_accumulate([], filtered.length, accumulator_completed)); | |
}); | |
} | |
}); | |
}); | |
}); | |
} | |
function process_and_build_all() { | |
// read all files in monitor path | |
return fs.readdir(monitor_path, on_html_directory_read); | |
} | |
function guarantee_paths_exist() { | |
if (!fs.existsSync(monitor_path)){ | |
fs.mkdirSync(compiled_path); | |
} | |
if (!fs.existsSync(compiled_path)){ | |
fs.mkdirSync(compiled_path); | |
} | |
if (!fs.existsSync(separate_compiled_path)){ | |
fs.mkdirSync(separate_compiled_path); | |
} | |
} | |
/** | |
1. Watch input files | |
2. Watch JS Separate files | |
*/ | |
function main() { | |
if(program.silent) { | |
program.debug = false; | |
} | |
qlog( | |
"Paths.", | |
"\nMonitor:", monitor_path, | |
"\nOutput:", compiled_path, | |
"\nSingle JS files:", separate_compiled_path, | |
"\n\n" | |
); | |
guarantee_paths_exist(); | |
if(program.onlyBuild) { | |
console.log("Will not watch files."); | |
process_and_build_all(); | |
} else { | |
fs.watch(monitor_path, { encoding: 'buffer' }, monitor_files_and_compile); | |
fs.watch(separate_compiled_path, { encoding: 'buffer' }, monitor_and_compile_combined_js); | |
} | |
} | |
main(); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment