ack-webpack
Version:
A code bundler that drastically reduces setup time by offering an init prompt of project setup questions and includes a fantastic browser reloader.
580 lines (490 loc) • 18.2 kB
JavaScript
const ackPath = require('ack-path')
const promiseSpawn = require('../promiseSpawn.function')
const install = require('../install.function')
const path = require('path')
const fs = require('fs')
const log = require("../log.function")
const promisePrompt = require('../promisePrompt.function')
const PackHelp = require('./package.help.js')
const packHelp = new PackHelp()
const ackPackHelp = new (require('./ack-package.help'))()
const appSrcPath = path.join('app','src')
let indexInputPath = path.join('index.pug')
//config templates
const typingsConfig = fs.readFileSync(path.join(__dirname,'lib','ack-app','typings.d.ts')).toString()
const tsConfig = require('./lib/ack-app/tsconfig.es5.json')
const tsAotConfig = require('./lib/ack-app/tsconfig.es5.aot.json')
function runPrompts(){
const schema = [{
description:'App src folder path',
name:'appSrcPath',
default:appSrcPath
},{
description:'Build output folder path',
name:'buildPath',
default: ()=>path.join(getAppSrcPath(), '../', 'www')
},{
description:'NEVER RUNS, just captures buildPath value to memory incase too many questions cause it to be lossed',
name:'buildPathMemory',
ask: ()=>getBuildPath() && false
}]
schema.push.apply(schema, getAssetSchema())
schema.push.apply(schema, getScriptsSchema())
return promisePrompt(schema)
}
function getAssetSchema(){
const schema = [{
description:'Create default tsconfig.json?',
name:'writeTsConfig',
default:'yes',
ask:()=>!fs.existsSync( path.join(process.cwd(), getAppSrcPath(), 'tsconfig.json') )
},{
description:'Create default tsconfig.aot.json?',
name:'writeTsAotConfig',
default:'yes',
ask:()=>!fs.existsSync( path.join(process.cwd(), getAppSrcPath(), 'tsconfig.aot.json') )
},{
description:'Create default typings.d.ts?',
name:'createTypings',
default:'yes',
ask:()=>!fs.existsSync( path.join(process.cwd(), getAppSrcPath(), 'typings.d.ts') )
}]
//index.ts
schema.push({
description:'Create index.ts',
name:'createIndexTs',
default:'yes',
ask:()=>!fs.existsSync( path.join(process.cwd(), getAppSrcPath(), 'index.ts') )
})
//index.aot.ts
schema.push({
description:'Create index.prod.ts',
name:'createIndexProdTs',
default:'yes',
ask:()=>!fs.existsSync( path.join(process.cwd(), getAppSrcPath(), 'index.prod.ts') )
})
//app.module.ts
schema.push({
description:'Create app.module.ts',
name:'createAppModuleTs',
default:'yes',
ask:()=>!fs.existsSync( path.join(process.cwd(), getAppSrcPath(), 'app.module.ts') )
})
return schema
}
function isPerformScripts(){
return promisePrompt.historyValueLikeTrue('addScripts')
}
function getBuildPath(){
return promisePrompt.historyValue('buildPath')
}
function getAppSrcPath(){
return promisePrompt.historyValue('appSrcPath')
}
function getScriptsSchema(){
const schema = []
schema.push({
description:'Would you like to choose convenience scripts to add to package.json?',
name:'addScripts',
default:'yes'
})
const compileScriptDefined = packHelp.scriptDefined("compile:templates")
if( !compileScriptDefined ){
schema.push({
description:'Add compile:templates convenient script to npm package',
name:'addCompileTemplates',
default:'yes',
ask: isPerformScripts
})
}
const compileAotDefined = packHelp.scriptDefined("compile:aot")
if( !compileAotDefined ){
schema.push({
description:'Add compile:aot convenient script to npm package',
name:'addCompileAot',
default:'yes',
ask: isPerformScripts
})
}
const buildJsDefined = packHelp.scriptDefined("build:js")
if( !buildJsDefined ){
schema.push({
description:'Add build:js convenient script to npm package',
name:'addBuildJs',
default:'yes',
ask: isPerformScripts
})
schema.push({
description:'Enter index.js file path',
name:'assetIndexFilePath',
default: ()=>path.join( getBuildPath(), 'assets','scripts','index.js'),
ask: ()=>isPerformScripts() && promisePrompt.historyValueLikeTrue('addBuildJs')
})
if( !packHelp.scriptDefined("watch:js") ){
schema.push({
description:'App has Html5 based routing',
name:'html5Mode',
default:'yes',
ask: ()=>isPerformScripts() && promisePrompt.historyValueLikeTrue('addBuildJs')
})
}
}
const buildCssDefined = packHelp.scriptDefined("build:css")
if( !buildCssDefined ){
schema.push({
description:'Add build:css convenient script to npm package',
name:'addBuildCss',
default:'yes',
ask: isPerformScripts
})
schema.push({
description:'Main SASS file path',
name:'sassInputPath',
default:()=>path.join( getAppSrcPath(), 'styles.scss' ),
ask: ()=>promisePrompt.historyValueLikeTrue('addScripts') && promisePrompt.historyValueLikeTrue('addBuildCss')
})
schema.push({
description:'Output SASS css file path',
name:'sassOutputPath',
default:(r)=>path.join( getBuildPath(), 'assets', 'styles', 'styles.css' ),
ask: ()=>promisePrompt.historyValueLikeTrue('addScripts') && promisePrompt.historyValueLikeTrue('addBuildCss')
})
}
const indexScriptDefined = packHelp.scriptDefined("build:index")
if( !indexScriptDefined ){
schema.push({
description:'Add build:index convenient script to npm package',
name:'addBuildIndex',
default:'yes',
ask: isPerformScripts
})
schema.push({
description:'Enter index.pug input file path',
name:'indexInputPath',
default: ()=>path.join(getAppSrcPath(), indexInputPath),
ask: ()=>isPerformScripts() && promisePrompt.isLikeTrue( promisePrompt.prompt.history('addBuildIndex').value )
})
schema.push({
description:'Create index.pug templating file',
name:'createIndex',
default:'yes',
ask: ()=>{
const indexInputPath = promisePrompt.prompt.history('indexInputPath').value
return isPerformScripts() && !fs.existsSync( path.join(process.cwd(),indexInputPath) )
}
})
schema.push({
description:'Build index.html via package script build:index',
name:'runBuildIndex',
default:'yes',
ask: ()=>isPerformScripts() && promisePrompt.isLikeTrue( promisePrompt.prompt.history('addBuildIndex').value )
})
}
schema.push({
description:'Update npm run build package.json script',
name:'manageBuildScript',
default:'yes',
ask: ()=>isPerformScripts()
})
schema.push({
description:'Update npm run watch package.json script',
name:'manageWatchScript',
default:'yes',
ask: ()=>isPerformScripts()
})
return schema
}
function paramAngularCompilers(){
let promise = Promise.resolve()
if( !promiseSpawn.isModuleInstalled('@angular/compiler') ){
promise = promise
.then( ()=>log('Installing @angular/compiler-cli to handle AoT compilation') )
.then( ()=>promiseSpawn.installPacks(['@angular/compiler'],{log:log}) )
}
if( !promiseSpawn.isModuleInstalled('@angular/compiler-cli') ){
promise = promise
.then( ()=>log('Installing @angular/compiler-cli to handle AoT compilation') )
.then( ()=>promiseSpawn.installPacks(['@angular/compiler-cli'],{log:log}) )
}
return promise
}
function processPrompts(results){
let promise = Promise.resolve()
if(!results)return promise;
let savePack = false
const installs = []
let paramNgCompilers = false
const myAppSrcPath = results.appSrcPath
promise = promise.then( ()=>ackPath( results.appSrcPath ).paramDir() )
promise = promise.then( ()=>processTsResults(results) )
/* build:index scripting */
const addBuildIndex = promisePrompt.isLikeTrue(results.addBuildIndex)
if(addBuildIndex){
savePack = true
packHelp.setScript(
"build:index",
"pug "+results.indexInputPath+" --out "+results.buildPath,
"Casts index.pug layout into index.html template to act as app entry file"
)
if( promisePrompt.isLikeTrue(results.createIndex)){
promise = promise.then(()=>{
log('Creating '+results.indexInputPath+'...')
const html = fs.readFileSync( path.join(__dirname,'lib','ack-angular','index.pug') ).toString()
const Path = ackPath(results.indexInputPath)
return Path.paramDir()
.then( ()=>Path.writeFile(html) )
})
}
}
/* end build:index scripting */
// compile:templates
if( promisePrompt.isLikeTrue(results.addCompileTemplates) ){
savePack = true
packHelp.setScript(
"compile:templates",
"ack-pug-bundler " + path.join(myAppSrcPath,"components","pugs") + " " + path.join(myAppSrcPath,"components","templates") + " --outType ts --oneToOne",
"Casts pugs folder, that has layout files like template.pug, into TypeScript compatible template.pug.ts files"
)
if( !packHelp.scriptDefined('watch:templates') ){
ackPath(myAppSrcPath).join("components","pugs").paramDir()
ackPath(myAppSrcPath).join("components","templates").paramDir()
packHelp.setScript(
"watch:templates",
"npm run compile:templates -- --watch",
"Watches pugs folder for changes to engage the script compile:templates"
)
}
if( !promiseSpawn.isModuleInstalled('ack-pug-bundler') ){
installs.push({name:'ack-pug-bundler', details:'Installing ack-pug-bundler to handle casting pugs to .ts files'})
}
}
// compile:aot
if( promisePrompt.isLikeTrue(results.addCompileAot) ){
savePack = true
packHelp.setScript(
"compile:aot",
"ngc -p " + path.join(myAppSrcPath,"tsconfig.aot.json"),
"Compiles TypeScript source files, Ahead of Time to reduce load times, to output folder"
)
paramNgCompilers = true
}
// build:js
if( promisePrompt.isLikeTrue(results.addBuildJs) ){
savePack = true
packHelp.setScript(
"build:js",
"node --max-old-space-size=8192 ./node_modules/ack-webpack/bin/cli "+path.join(myAppSrcPath,"index.prod.ts")+" "+results.assetIndexFilePath+" --production --project " + path.join(myAppSrcPath,"tsconfig.json"),
"Builds one TypeScript source file into a final www output file. Node memory has been increased to cover large app builds"
)
if( !packHelp.scriptDefined("watch:js") ){
const htmlMode = promisePrompt.isLikeTrue(results.html5Mode) ? ' --html5Mode' : ''
packHelp.setScript(
"watch:js",
"ack-webpack "+path.join(myAppSrcPath,"index.ts")+" "+results.assetIndexFilePath+" --production --project " + path.join(myAppSrcPath,"tsconfig.json") + htmlMode + " --watch --browser="+results.buildPath,
"Builds one TypeScript source file into a final www output file. Node memory has been increased to cover large app builds"
)
}
paramNgCompilers = true
}
// build:css
if( promisePrompt.isLikeTrue(results.addBuildCss) ){
savePack = true
packHelp.setScript(
"build:css",
"ack-sass "+results.sassInputPath+" "+results.sassOutputPath+" --production",
"Builds one SASS source file into a final css output file"
)
if( !packHelp.scriptDefined("watch:css") ){
packHelp.setScript(
"watch:css",
"ack-sass "+results.sassInputPath+" "+results.sassOutputPath+" --watch",
"Builds one SASS source file into a final css output file"
)
}
if( !promiseSpawn.isModuleInstalled('ack-sass') ){
installs.push({name:'ack-sass', details:'Installing ack-sass to handle casting .scss files to .css file'})
}
//default styles.scss file
const Path = ackPath( process.cwd() ).join( results.sassInputPath )
promise = promise.then(()=>Path.File().exists())
.then( defined=>{
if(defined)return
log('Created empty '+results.sassInputPath+' file')
return Path.writeFile( ackPath( __dirname ).join('lib','ack-angular','styles.scss').File().sync().readAsString() )
})
}
const build = promisePrompt.isLikeTrue(results.manageBuildScript)
if( build ){
savePack = true
const buildArray = (packHelp.getScript("build") || 'npm-run-all -s').split(' ')
const builders = [
"copy:fonts",
"build:css",
"build:index",
"compile:prefx",
"compile:templates",
"compile:aot",
"build:js"
]
builders.forEach(name=>{
if( !packHelp.scriptDefined(name) || buildArray.indexOf(name)>=0 )return
buildArray.push(name)
})
packHelp.setScript(
"build",
buildArray.join(' '),
"Builds TypeScript files to output folder"
)
paramNgCompilers = true
}
const watch = promisePrompt.isLikeTrue(results.manageWatchScript)
if( watch ){
savePack = true
const watchArray = (packHelp.getScript("watch") || 'npm-run-all --parallel').split(' ')
const watchers = [
"compile:aot",//ensure watched AoT files exist
"copy:fonts",//if fonts were changed before watch, they will be rendered here
"compile:templates",//if templates were changed before watch, they will be rendered here
"watch:css",
"watch:templates",
"watch:js"
]
watchers.forEach(name=>{
if( !packHelp.scriptDefined(name) || watchArray.indexOf(name)>=0 )return
watchArray.push(name)
})
packHelp.setScript(
"watch",
watchArray.join(' '),
"Employes other watch script to particpate in a combined"
)
}
//AFTER package scripting
if( savePack ){
promise = promise.then( ()=>packHelp.save() )
}
if( (build || watch) && !promiseSpawn.isModuleInstalled('npm-run-all') ){
promise = promise
.then( ()=>log('To handle running multiple scripts across any device, npm-run-all will be installed') )
.then( ()=>promiseSpawn.installPacks(['npm-run-all']) )
}
if( paramNgCompilers ){
promise = promise.then( paramAngularCompilers )
}
//INSTALLS MUST COME AFTER package manipulations
if( installs.length ){
installs.forEach(install=>{
promise = promise
.then( ()=>log(install.details) )
.then( ()=>promiseSpawn.installPacks([install.name],{log:log}) )
})
}
//build index.ts
if( promisePrompt.isLikeTrue(results.createIndexTs) ){
promise = promise.then(()=>{
const writeFile = path.join(getAppSrcPath(), 'index.ts')
log('Creating '+writeFile+'...')
const contents = fs.readFileSync( path.join(__dirname,'lib','ack-angular','index.ts') ).toString()
const Path = ackPath(writeFile)
return Path.paramDir()
.then( ()=>Path.writeFile(contents) )
})
}
//build index.aot.ts
if( promisePrompt.isLikeTrue(results.createIndexProdTs) ){
promise = promise.then(()=>{
const writeFile = path.join(getAppSrcPath(), 'index.aot.ts')
log('Creating '+writeFile+'...')
const contents = fs.readFileSync( path.join(__dirname,'lib','ack-angular','index.aot.ts') ).toString()
const Path = ackPath(writeFile)
return Path.paramDir()
.then( ()=>Path.writeFile(contents) )
})
}
//build app.module.ts
if( promisePrompt.isLikeTrue(results.createAppModuleTs) ){
promise = promise.then(()=>{
const writeFile = path.join(getAppSrcPath(), 'app.module.ts')
log('Creating '+writeFile+'...')
const contents = fs.readFileSync( path.join(__dirname,'lib','ack-angular','app.module.ts') ).toString()
const Path = ackPath(writeFile)
return Path.paramDir()
.then( ()=>Path.writeFile(contents) )
})
}
/* after run scripts */
if( promisePrompt.isLikeTrue(results.runBuildIndex) ){
promise = promise
.then(()=>log('Building index.html file...'))
.then( ()=>promiseSpawn(['npm','run','build:index'], {log:log}) )
}
if( promisePrompt.isLikeTrue(results.runPrefx) ){
promise = promise
.then(()=>log('Creating ack-angular-fx prefx.ts file...'))
.then( ()=>promiseSpawn(['npm','run','compile:prefx'], {log:log}) )
}
/* end : after run scripts */
return promise
.then( ()=>ackPackHelp.updatePrompt("init:ack-app", results).save() )
}
function processTsResults(results){
let promise = Promise.resolve()
const appRoot = getAppSrcPath()
const tsOptions = {
writeTsAotConfig : promisePrompt.isLikeTrue(results.writeTsAotConfig),
writeTsConfig : promisePrompt.isLikeTrue(results.writeTsConfig),
createTypings : promisePrompt.isLikeTrue(results.createTypings)
}
if(tsOptions.writeTsConfig || tsOptions.writeTsAotConfig || tsOptions.createTypings){
promise = promise.then( ()=>ackPath(appRoot).param() )
}
if(tsOptions.writeTsConfig){
promise = promise.then( ()=>writeTsConfig(appRoot, tsOptions) )
}
if(tsOptions.writeTsAotConfig){
promise = promise.then( ()=>writeTsAotConfig(appRoot, tsOptions) )
}
if(tsOptions.createTypings){
promise = promise.then( ()=>createTypings(appRoot, tsOptions) )
}
return promise
}
function writeTsConfig(appRoot, options){
const filePath = path.join(appRoot,'tsconfig.json')
const exists = fs.existsSync( filePath )
if(exists)return
const config = tsConfig
if(options && options.createTypings){
config.files = config.files || []
config.files.push('typings.d.ts')
}
fs.writeFileSync(filePath, JSON.stringify(config, null, 2))
log('created',filePath)
}
function writeTsAotConfig(appRoot, options){
const filePath = path.join(appRoot,'tsconfig.aot.json')
const exists = fs.existsSync( filePath )
if(exists)return
const config = tsAotConfig
if(options && options.createTypings){
config.files = config.files || []
config.files.push('typings.d.ts')
}
fs.writeFileSync(filePath, JSON.stringify(config, null, 2))
log('created',filePath)
}
function createTypings(appRoot, options){
const filePath = path.join(appRoot,'typings.d.ts')
const exists = fs.existsSync( filePath )
if(exists)return
fs.writeFileSync(filePath, typingsConfig)
log('created',filePath)
}
runPrompts()
.then(processPrompts)
.catch(e=>{
if(e.message=='canceled'){
return
}
log.error(e)
})