fusox
Version:
Command line wrapper for fuse-box
267 lines (220 loc) • 8.08 kB
JavaScript
const {findExistingFile, parseBoolean, printHeader} = require('../helpers')
const process = require('process')
const util = require('util')
const path = require('path')
const {existsSync} = require('fs')
const {spawn} = require('child_process')
const _ = require('lodash')
const fsb = require('fuse-box')
const stripAnsi = require('strip-ansi')
const replaceInFile = require('replace-in-file')
const which = require('npm-which')(__dirname)
const {YAMLPlugin} = require('fuse-box-yaml')
module.exports = {parseBuildLibraryFlags, buildLibraryCommand}
function parseBuildLibraryFlags (args, flags) {
// let treeshakeCode = args.treeshake === undefined ? flags.mode === 'production' : parseBoolean(args.treeshake)
// let isolateBundleApi = args.isolate === undefined ? flags.mode === 'production' : parseBoolean(args.isolate)
let customMainFile = args.main
let customMainFilePath = customMainFile && flags.resolveSourcePath(customMainFile)
let defaultMainFilePath = flags.resolveSourcePath('index.ts')
let mainFilePath = findExistingFile(customMainFilePath, defaultMainFilePath)
if (!mainFilePath || !existsSync(mainFilePath)) {
throw new Error(util.format(
'Could not find main file at "%s" or "%s", see "--main" option',
flags.resolveSourcePathRelative(customMainFilePath),
flags.resolveSourcePathRelative(defaultMainFilePath)
))
}
let mainFile = mainFilePath && path.basename(mainFilePath)
let mainFilePathRelative = flags.resolveSourcePathRelative(mainFilePath)
let moduleFormats = (args.module || '').split(/\s|,/)
parseBoolean(args.esm) && moduleFormats.push('esm')
parseBoolean(args.cjs) && moduleFormats.push('cjs')
parseBoolean(args.web) && moduleFormats.push('web')
parseBoolean(args.all) && moduleFormats.push('cjs', 'esm', 'web')
moduleFormats = _.uniq(moduleFormats)
.filter((format) => ['esm', 'cjs', 'web'].indexOf(format) !== -1)
if (moduleFormats.length === 0) {
throw new Error('You must specify module format, see --module flag')
}
let appName = flags.appName || mainFile.replace(path.extname(mainFile), '')
return {
...flags,
moduleFormats,
mainFile,
mainFilePath,
mainFilePathRelative,
// treeshakeCode,
// isolateBundleApi,
appName
}
}
function parseModuleSpecificLibFlags (moduleFormat, flags) {
let typescriptModuleFormat = moduleFormat === 'esm' ? 'es6' : 'commonjs'
let typescriptTarget = moduleFormat === 'esm' ? 'es6' : 'es5'
let moduleFormatSubdirectory = moduleFormat
let buildFilePath = flags.resolveDestinationPath(
moduleFormatSubdirectory,
flags.mainFilePathRelative.replace(path.extname(flags.mainFilePathRelative), '.js')
)
let compilerOptions = existsSync(flags.tsconfigPath) ? (require(flags.tsconfigPath) || {compilerOptions: {}}).compilerOptions : {}
let compilerFlags = []
compilerFlags.push(flags.mainFilePath)
compilerFlags.push('--outDir', flags.resolveDestinationPath(moduleFormatSubdirectory))
compilerFlags.push('--module', typescriptModuleFormat)
compilerFlags.push('--target', typescriptTarget)
compilerFlags.push('--lib', 'es2015,dom')
compilerFlags.push('--pretty')
compilerFlags.push('--noEmitOnError')
compilerFlags.push('--jsx', 'react')
compilerFlags.push('--declaration')
compilerFlags.push('--moduleResolution', 'node')
if (flags.watchForChanges) {
compilerFlags.push('--watch')
}
if (flags.generateSourceMaps) {
compilerFlags.push('--sourceMap')
}
_.keys(compilerOptions).map((key) => {
let value = compilerOptions[key]
if (value === true) {
value = 'true'
} else if (value === false) {
value = 'false'
} else if (_.isArray(value)) {
value = value.join(',')
}
compilerFlags.push(util.format('--%s', key), value)
})
return {
...flags,
moduleFormat,
buildFilePath,
typescriptModuleFormat,
moduleFormatSubdirectory,
compilerFlags
}
}
function buildLibraryCommand (flags) {
let buildPromises = flags.moduleFormats.map((moduleFormat) => {
let moduleFlags = parseModuleSpecificLibFlags(moduleFormat, flags)
if (moduleFormat === 'web') {
return buildTroughFuseBox(moduleFlags)
} else if (['cjs', 'esm'].indexOf(moduleFormat) !== -1) {
return buildTroughTypescript(moduleFlags)
}
})
return Promise.all(buildPromises)
}
function buildTroughFuseBox (flags) {
if (flags.globalName) {
printHeader('Main package exported as window["%s"] in module "web"', flags.globalName)
}
let fuse = fsb.FuseBox.init({
target: 'browser',
homeDir: flags.sourcePath,
modulesFolder: flags.nodeModulesPath,
output: util.format('%s/web/$name.js', flags.destinationPath),
tsConfig: flags.tsconfigPath,
sourceMaps: flags.generateSourceMaps,
hash: false,
cache: flags.useCache,
log: true,
ensureTsConfig: false,
globals: flags.globalName ? {default: flags.globalName} : null,
showErrors: true,
plugins: [
YAMLPlugin(),
fsb.JSONPlugin(),
fsb.EnvPlugin(flags.envConfig),
fsb.CopyPlugin({
useDefault: false,
files: flags.assetFilesExtensions,
dest: '.',
resolve: flags.assetsMainPath
}),
fsb.ReplacePlugin(flags.processEnvConfig),
flags.mode === 'production' && flags.uglifyCode && fsb.UglifyESPlugin(),
// fsb.QuantumPlugin({
// target: 'browser',
// treeshake: flags.treeshakeCode,
// uglify: flags.uglifyCode,
// containedAPI: true,
// bakeApiIntoBundle: flags.appName
// })
]
})
let mainBundle = fuse.bundle(flags.appName)
.instructions(util.format('>%s', flags.mainFilePathRelative))
if (flags.watchForChanges && flags.mode === 'development') {
mainBundle.watch()
}
printHeader('Launching fuse-box for "library %s"', flags.moduleFormat)
return fuse.run()
.then(() => {
if ( ! flags.watchForChanges) {
printHeader('Module "%s" compiled', flags.moduleFormat)
}
})
}
function buildTroughTypescript (flags) {
return new Promise((resolve) => {
// todo: switch to ts js api instead of using cli
let child = runCompiler(flags)
let hasErrors = false
let proc = null
child.stdout.on('data', (data) => {
let message = stripAnsi(data.toString())
let compilationComplete = false
if (message.indexOf('error TS') !== -1 || message.indexOf('warning TS') !== -1) {
console.log(data.toString())
hasErrors = true
}
if (message.indexOf('Compilation complete') !== -1) {
if (hasErrors) {
hasErrors = false
} else {
compilationComplete = true
}
// kill previous process
if (proc) {
proc.kill()
proc = null
}
}
if (compilationComplete && ! hasErrors) {
replaceProcessEnv(flags)
if (flags.watchForChanges) {
printHeader('Typescript compiled for module "%s"', flags.moduleFormat)
}
resolve()
}
})
// finish when the process ends
child.on('close', () => {
if (flags.watchForChanges) {
process.exit(0)
} else {
replaceProcessEnv(flags)
if (existsSync(flags.buildFilePath)) {
printHeader('Module "%s" compiled', flags.moduleFormat)
resolve()
} else {
throw new Error(util.format('Could not compile for module "%s", something went terribly wrong', flags.moduleFormat))
}
}
})
})
}
function replaceProcessEnv (flags) {
let files = util.format('%s/**/*.js', flags.destinationPath)
let from = _.keys(flags.envConfig).map((k) => util.format('process.env.%s', k))
let to = _.values(flags.envConfig).map((v) => JSON.stringify(v))
return replaceInFile.sync({files, from, to})
}
function runCompiler (flags) {
return spawn(which.sync('tsc'), flags.compilerFlags, {shell: true})
}
function runMainFile (flags) {
return spawn(util.format('node %s', flags.buildFilePath), {shell: true, stdio: 'inherit'})
}