Skip to content
This repository was archived by the owner on May 1, 2020. It is now read-only.

Commit

Permalink
fix(build): better support for saving multiple files at a time
Browse files Browse the repository at this point in the history
  • Loading branch information
danbucholtz authored Nov 19, 2016
1 parent 64eb845 commit 254bb6c
Show file tree
Hide file tree
Showing 14 changed files with 208 additions and 234 deletions.
84 changes: 66 additions & 18 deletions src/build.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { BuildContext, BuildState, BuildUpdateMessage } from './util/interfaces';
import { FILE_CHANGE_EVENT, FILE_DELETE_EVENT } from './util/constants';
import { BuildContext, BuildState, BuildUpdateMessage, ChangedFile } from './util/interfaces';
import { BuildError } from './util/errors';
import { readFileAsync } from './util/helpers';
import { bundle, bundleUpdate } from './bundle';
import { clean } from './clean';
import { copy } from './copy';
Expand Down Expand Up @@ -121,7 +123,7 @@ function buildDev(context: BuildContext) {
}


export function buildUpdate(event: string, filePath: string, context: BuildContext) {
export function buildUpdate(changedFiles: ChangedFile[], context: BuildContext) {
return new Promise(resolve => {
const logger = new Logger('build');

Expand Down Expand Up @@ -151,13 +153,20 @@ export function buildUpdate(event: string, filePath: string, context: BuildConte
// this one is useful when only a sass changed happened
// and the webpack only needs to livereload the css
// but does not need to do a full page refresh
emit(EventType.FileChange, resolveValue.changedFile);
emit(EventType.FileChange, resolveValue.changedFiles);
}

if (filePath.endsWith('.ts')) {
let requiresLintUpdate = false;
for (const changedFile of changedFiles) {
if (changedFile.ext === '.ts' && changedFile.event === 'ch') {
requiresLintUpdate = true;
break;
}
}
if (requiresLintUpdate) {
// a ts file changed, so let's lint it too, however
// this task should run as an after thought
lintUpdate(event, filePath, context);
lintUpdate(changedFiles, context);
}

logger.finish('green', true);
Expand All @@ -170,16 +179,16 @@ export function buildUpdate(event: string, filePath: string, context: BuildConte

// kick off all the build tasks
// and the tasks that can run parallel to all the build tasks
const buildTasksPromise = buildUpdateTasks(event, filePath, context);
const parallelTasksPromise = buildUpdateParallelTasks(event, filePath, context);
const buildTasksPromise = buildUpdateTasks(changedFiles, context);
const parallelTasksPromise = buildUpdateParallelTasks(changedFiles, context);

// whether it was resolved or rejected, we need to do the same thing
buildTasksPromise
.then(buildTasksDone)
.catch(() => {
buildTasksDone({
requiresAppReload: false,
changedFile: filePath
changedFiles: changedFiles
});
});
});
Expand All @@ -189,18 +198,21 @@ export function buildUpdate(event: string, filePath: string, context: BuildConte
* Collection of all the build tasks than need to run
* Each task will only run if it's set with eacn BuildState.
*/
function buildUpdateTasks(event: string, filePath: string, context: BuildContext) {
function buildUpdateTasks(changedFiles: ChangedFile[], context: BuildContext) {
const resolveValue: BuildTaskResolveValue = {
requiresAppReload: false,
changedFile: filePath
changedFiles: []
};

return Promise.resolve()
.then(() => {
return loadFiles(changedFiles, context);
})
.then(() => {
// TEMPLATE
if (context.templateState === BuildState.RequiresUpdate) {
resolveValue.requiresAppReload = true;
return templateUpdate(event, filePath, context);
return templateUpdate(changedFiles, context);
}
// no template updates required
return Promise.resolve();
Expand All @@ -213,7 +225,7 @@ function buildUpdateTasks(event: string, filePath: string, context: BuildContext
// we've already had a successful transpile once, only do an update
// not that we've also already started a transpile diagnostics only
// build that only needs to be completed by the end of buildUpdate
return transpileUpdate(event, filePath, context);
return transpileUpdate(changedFiles, context);

} else if (context.transpileState === BuildState.RequiresBuild) {
// run the whole transpile
Expand All @@ -229,7 +241,7 @@ function buildUpdateTasks(event: string, filePath: string, context: BuildContext
if (context.bundleState === BuildState.RequiresUpdate) {
// we need to do a bundle update
resolveValue.requiresAppReload = true;
return bundleUpdate(event, filePath, context);
return bundleUpdate(changedFiles, context);

} else if (context.bundleState === BuildState.RequiresBuild) {
// we need to do a full bundle build
Expand All @@ -244,14 +256,30 @@ function buildUpdateTasks(event: string, filePath: string, context: BuildContext
// SASS
if (context.sassState === BuildState.RequiresUpdate) {
// we need to do a sass update
return sassUpdate(event, filePath, context).then(outputCssFile => {
resolveValue.changedFile = outputCssFile;
return sassUpdate(changedFiles, context).then(outputCssFile => {
const changedFile: ChangedFile = {
event: FILE_CHANGE_EVENT,
ext: '.css',
filePath: outputCssFile
};

context.fileCache.set(outputCssFile, { path: outputCssFile, content: outputCssFile});

resolveValue.changedFiles.push(changedFile);
});

} else if (context.sassState === BuildState.RequiresBuild) {
// we need to do a full sass build
return sass(context).then(outputCssFile => {
resolveValue.changedFile = outputCssFile;
const changedFile: ChangedFile = {
event: FILE_CHANGE_EVENT,
ext: '.css',
filePath: outputCssFile
};

context.fileCache.set(outputCssFile, { path: outputCssFile, content: outputCssFile});

resolveValue.changedFiles.push(changedFile);
});
}
// no sass build required
Expand All @@ -262,17 +290,37 @@ function buildUpdateTasks(event: string, filePath: string, context: BuildContext
});
}

function loadFiles(changedFiles: ChangedFile[], context: BuildContext) {
// UPDATE IN-MEMORY FILE CACHE
let promises: Promise<any>[] = [];
for (const changedFile of changedFiles) {
if (changedFile.event === FILE_DELETE_EVENT) {
// remove from the cache on delete
context.fileCache.remove(changedFile.filePath);
} else {
// load the latest since the file changed
const promise = readFileAsync(changedFile.filePath);
promises.push(promise);
promise.then((content: string) => {
context.fileCache.set(changedFile.filePath, { path: changedFile.filePath, content: content});
});
}
}

return Promise.all(promises);
}

interface BuildTaskResolveValue {
requiresAppReload: boolean;
changedFile: string;
changedFiles: ChangedFile[];
}

/**
* parallelTasks are for any tasks that can run parallel to the entire
* build, but we still need to make sure they've completed before we're
* all done, it's also possible there are no parallelTasks at all
*/
function buildUpdateParallelTasks(event: string, filePath: string, context: BuildContext) {
function buildUpdateParallelTasks(changedFiles: ChangedFile[], context: BuildContext) {
const parallelTasks: Promise<any>[] = [];

if (context.transpileState === BuildState.RequiresUpdate) {
Expand Down
8 changes: 4 additions & 4 deletions src/bundle.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BuildContext } from './util/interfaces';
import { BuildContext, ChangedFile } from './util/interfaces';
import { BuildError, IgnorableError } from './util/errors';
import { generateContext, BUNDLER_ROLLUP } from './util/config';
import { rollup, rollupUpdate, getRollupConfig, getOutputDest as rollupGetOutputDest } from './rollup';
Expand All @@ -24,15 +24,15 @@ function bundleWorker(context: BuildContext, configFile: string) {
}


export function bundleUpdate(event: string, filePath: string, context: BuildContext) {
export function bundleUpdate(changedFiles: ChangedFile[], context: BuildContext) {
if (context.bundler === BUNDLER_ROLLUP) {
return rollupUpdate(event, filePath, context)
return rollupUpdate(changedFiles, context)
.catch(err => {
throw new BuildError(err);
});
}

return webpackUpdate(event, filePath, context, null)
return webpackUpdate(changedFiles, context, null)
.catch(err => {
if (err instanceof IgnorableError) {
throw err;
Expand Down
8 changes: 4 additions & 4 deletions src/dev-server/live-reload.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ChangedFile } from '../util/interfaces';
import { hasDiagnostics } from '../logger/logger-diagnostics';
import * as path from 'path';
import * as tinylr from 'tiny-lr';
Expand All @@ -9,14 +10,13 @@ export function createLiveReloadServer(config: ServeConfig) {
const liveReloadServer = tinylr();
liveReloadServer.listen(config.liveReloadPort, config.host);

function fileChange(filePath: string | string[]) {
function fileChange(changedFiles: ChangedFile[]) {
// only do a live reload if there are no diagnostics
// the notification server takes care of showing diagnostics
if (!hasDiagnostics(config.buildDir)) {
const files = Array.isArray(filePath) ? filePath : [filePath];
liveReloadServer.changed({
body: {
files: files.map(f => '/' + path.relative(config.wwwDir, f))
files: changedFiles.map(changedFile => '/' + path.relative(config.wwwDir, changedFile.filePath))
}
});
}
Expand All @@ -25,7 +25,7 @@ export function createLiveReloadServer(config: ServeConfig) {
events.on(events.EventType.FileChange, fileChange);

events.on(events.EventType.ReloadApp, () => {
fileChange('index.html');
fileChange([{ event: 'change', ext: '.html', filePath: 'index.html'}]);
});
}

Expand Down
18 changes: 13 additions & 5 deletions src/lint.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { access } from 'fs';
import { BuildContext, TaskInfo } from './util/interfaces';
import { BuildContext, ChangedFile, TaskInfo } from './util/interfaces';
import { BuildError } from './util/errors';
import { createProgram, findConfiguration, getFileNames } from 'tslint';
import { generateContext, getUserConfigFile } from './util/config';
Expand Down Expand Up @@ -31,12 +31,13 @@ export function lintWorker(context: BuildContext, configFile: string) {
}


export function lintUpdate(event: string, filePath: string, context: BuildContext) {
export function lintUpdate(changedFiles: ChangedFile[], context: BuildContext) {
const changedTypescriptFiles = changedFiles.filter(changedFile => changedFile.ext === '.ts');
return new Promise(resolve => {
// throw this in a promise for async fun, but don't let it hang anything up
const workerConfig: LintWorkerConfig = {
configFile: getUserConfigFile(context, taskInfo, null),
filePath: filePath
filePaths: changedTypescriptFiles.map(changedTypescriptFile => changedTypescriptFile.filePath)
};

runWorker('lint', 'lintUpdateWorker', context, workerConfig);
Expand All @@ -49,7 +50,7 @@ export function lintUpdateWorker(context: BuildContext, workerConfig: LintWorker
return getLintConfig(context, workerConfig.configFile).then(configFile => {
// there's a valid tslint config, let's continue (but be quiet about it!)
const program = createProgram(configFile, context.srcDir);
return lintFile(context, program, workerConfig.filePath);
return lintFiles(context, program, workerConfig.filePaths);
}).catch(() => {
});
}
Expand All @@ -66,6 +67,13 @@ function lintApp(context: BuildContext, configFile: string) {
return Promise.all(promises);
}

function lintFiles(context: BuildContext, program: ts.Program, filePaths: string[]) {
const promises: Promise<void>[] = [];
for (const filePath of filePaths) {
promises.push(lintFile(context, program, filePath));
}
return Promise.all(promises);
}

function lintFile(context: BuildContext, program: ts.Program, filePath: string) {
return new Promise((resolve) => {
Expand Down Expand Up @@ -162,5 +170,5 @@ const taskInfo: TaskInfo = {

export interface LintWorkerConfig {
configFile: string;
filePath: string;
filePaths: string[];
}
4 changes: 2 additions & 2 deletions src/rollup.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BuildContext, BuildState, TaskInfo } from './util/interfaces';
import { BuildContext, BuildState, ChangedFile, TaskInfo } from './util/interfaces';
import { BuildError } from './util/errors';
import { fillConfigDefaults, generateContext, getUserConfigFile, replacePathVars } from './util/config';
import { ionCompiler } from './plugins/ion-compiler';
Expand All @@ -25,7 +25,7 @@ export function rollup(context: BuildContext, configFile: string) {
}


export function rollupUpdate(event: string, filePath: string, context: BuildContext) {
export function rollupUpdate(changedFiles: ChangedFile[], context: BuildContext) {
const logger = new Logger('rollup update');

const configFile = getUserConfigFile(context, taskInfo, null);
Expand Down
10 changes: 7 additions & 3 deletions src/sass.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { basename, dirname, join, sep } from 'path';
import { BuildContext, BuildState, TaskInfo } from './util/interfaces';
import { BuildContext, BuildState, ChangedFile, TaskInfo } from './util/interfaces';
import { BuildError } from './util/errors';
import { bundle } from './bundle';
import { ensureDirSync, readdirSync, writeFile } from 'fs-extra';
Expand Down Expand Up @@ -31,7 +31,7 @@ export function sass(context?: BuildContext, configFile?: string) {
}


export function sassUpdate(event: string, filePath: string, context: BuildContext) {
export function sassUpdate(changedFiles: ChangedFile[], context: BuildContext) {
const configFile = getUserConfigFile(context, taskInfo, null);

const logger = new Logger('sass update');
Expand Down Expand Up @@ -60,7 +60,7 @@ export function sassWorker(context: BuildContext, configFile: string) {
return Promise.all(bundlePromise).then(() => {
clearDiagnostics(context, DiagnosticsType.Sass);

const sassConfig: SassConfig = fillConfigDefaults(configFile, taskInfo.defaultConfigFile);
const sassConfig: SassConfig = getSassConfig(context, configFile);

// where the final css output file is saved
if (!sassConfig.outFile) {
Expand Down Expand Up @@ -88,6 +88,10 @@ export function sassWorker(context: BuildContext, configFile: string) {
});
}

export function getSassConfig(context: BuildContext, configFile: string): SassConfig {
configFile = getUserConfigFile(context, taskInfo, configFile);
return fillConfigDefaults(configFile, taskInfo.defaultConfigFile);
}

function generateSassData(context: BuildContext, sassConfig: SassConfig) {
/**
Expand Down
Loading

0 comments on commit 254bb6c

Please sign in to comment.