UNPKG

fusox

Version:

Command line wrapper for fuse-box

267 lines (220 loc) 8.08 kB
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'}) }