@polight/lego
Version:
Tiny Web-Components lib for future-proof HTML mentors
132 lines (121 loc) • 4.34 kB
JavaScript
import fs from 'fs'
import os from 'os'
import { execFileSync } from 'child_process'
import { createComponent, generateIndex } from '../src/compiler/transpiler.js'
import defaultConfig from '../src/compiler/config.js'
// Read the config from the args
const args = process.argv
const [sourceDir, targetDir] = args.slice(2).filter(a => !a.startsWith('-'))
const argsConfig = {
sourceDir,
targetDir,
watch: args.indexOf('-w') >= 0
}
/**
* Merge 2 objects.
* This is different from `Object.assign` as it will only overwrite
* property that have a defined value.
* @param {Object} native
* @param {Object} override
* @returns {Object} the graal you prayed for 🤲
*/
function mergeObjects(native, override) {
return Object.keys(native).reduce((obj, key) => {
obj[key] = (key in override && typeof override[key] !== 'undefined')
? override[key]
: native[key]
return obj
}, {})
}
/**
* Detect if the running system is a Microsoft Windows®
* @returns boolean
*/
function isWindows() {
return os.platform() === 'win32'
}
/**
* Browse files in a directory considering some extensions.
*
* @param {string} dirname Name of the directory from where to list
* @param {string} extensions list of extensions to read from
* @returns {Array} list of file path
*/
async function walkDir(dirname, extensions) {
const stdout = isWindows()
? execFileSync('cmd', ['/c', 'dir', '/s', '/b', fs.realpathSync(dirname)])
: execFileSync('find', [dirname])
const dirs = String(stdout).split(os.EOL).filter(d => d)
if(!extensions) return dirs
return dirs.filter(d => extensions.includes(d.split('.').splice(-1)[0]))
}
/**
* Convert a series of HTML components into JS components.
*
* @param {string} sourceDir directory from where to read HTML components
* @param {string} targetDir directory where to write JS components
* @param {Object} config
* @returns {Object} { component, filename } object
*/
async function compile(sourceDir, targetDir, config) {
const filenames = await walkDir(sourceDir, ['html'])
fs.mkdirSync(targetDir, { recursive: true })
return filenames.map(f => {
const pattern = isWindows() ? /.*\\(.+)\.html/ : /.*\/(.+)\.html/
const filename = f.replace(pattern, '$1')
const component = createComponent({
html: fs.readFileSync(f, 'utf8'),
name: filename,
config
})
fs.writeFileSync(`${targetDir}/${filename}.js`, component.content, 'utf8')
return { component, filename }
})
}
/**
* Create an index.js file in a directory
* @param {string} targetDir Directory where to write the file
* @param {string} filenames List of files name to interpret as components
*/
async function writeIndex(targetDir, filenames) {
const content = generateIndex(filenames)
fs.writeFileSync(`${targetDir}/index.js`, content, 'utf8')
}
/**
* Build the whole stuff:
* 1. calculate best configuration
* 2. compile sourceDir to targetDir
* 3. create the index.js and its content
* 4. if `watch`ing for changes, run `compile` over again
*/
async function build() {
let userConfig = {}
try {
const pathPrefix = isWindows() ? 'file://' : ''
console.debug(`${pathPrefix + process.cwd()}/lego.config.js`)
const content = await import(`${pathPrefix + process.cwd()}/lego.config.js`)
userConfig = content.default
}
catch {
console.warn('⚠️ Missing lego.config.js file, building with defaults.')
}
const config = mergeObjects(mergeObjects(defaultConfig, userConfig), argsConfig)
const { sourceDir, targetDir } = config
const compiled = await compile(sourceDir, targetDir, config)
writeIndex(targetDir, compiled.map(c => c.filename))
const names = compiled.map(c => c.component.name)
console.info('⚙️ Current configuration:', config)
console.info(`🏗 ${names.length} component${names.length > 1 ? 's were' : ' was'} compiled into "${config.targetDir}": ${names.join(', ')}.`)
if (config.watch) {
console.info(`\n👀 Watching changes in ${sourceDir}…`)
fs.watch(sourceDir, async (event, filename) => {
const compiled = await compile(sourceDir, targetDir, config)
if (event === 'rename') {
writeIndex(targetDir, compiled.map(c => c.filename))
}
console.info(` ♻️ ${filename} was recompiled!`)
})
}
}
build()