diff --git a/config/webpack.config.js b/config/webpack.config.js index 86bb0ac6..67092e61 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -1,14 +1,6 @@ var path = require('path'); var webpack = require('webpack'); - - - -// for prod builds, we have already done AoT and AoT writes to disk -// so read the JS file from disk -// for dev buids, we actually want to pass in .ts files since we -// don't have .js files on disk, they're exclusively in memory - function getEntryPoint() { if (process.env.IONIC_ENV === 'prod') { return '{{TMP}}/app/main.prod.js'; @@ -21,6 +13,7 @@ function getPlugins() { return [ // This helps ensure the builds are consistent if source hasn't changed: new webpack.optimize.OccurrenceOrderPlugin(), + // Try to dedupe duplicated modules, if any: // Add this back in when Angular fixes the issue: https://github.com/angular/angular-cli/issues/1587 //new DedupePlugin() @@ -29,6 +22,20 @@ function getPlugins() { return []; } +function getSourcemapLoader() { + if (process.env.IONIC_ENV === 'prod') { + // TODO figure out the disk loader, it's not working yet + return []; + } + + return [ + { + test: /\.js$/, + loader: path.resolve(path.join(process.cwd(), 'node_modules', '@ionic', 'app-scripts', 'dist', 'loaders', 'typescript-sourcemap-loader-memory.js')) + } + ]; +} + module.exports = { entry: getEntryPoint(), output: { @@ -47,7 +54,7 @@ module.exports = { test: /\.json$/, loader: 'json' } - ] + ].concat(getSourcemapLoader()) }, plugins: getPlugins(), diff --git a/src/loaders/typescript-sourcemap-loader-disk.ts b/src/loaders/typescript-sourcemap-loader-disk.ts new file mode 100644 index 00000000..21113a35 --- /dev/null +++ b/src/loaders/typescript-sourcemap-loader-disk.ts @@ -0,0 +1,59 @@ +import { Logger } from '../util/logger'; +import { changeExtension, readFileAsync } from '../util/helpers'; + +module.exports = function typescriptSourcemapLoaderDisk(source: string, map: string) { + this.cacheable(); + var callback = this.async(); + + if ( this.resourcePath.indexOf('node_modules') > -1) { + // it's not a source file, so use the default + callback(null, source, map); + } else { + // it's a src file + loadBetterSourceMap(this.resourcePath, callback, source, map); + } +}; + +function loadBetterSourceMap(javascriptFilePath: string, callback: Function, originalSource: any, originalMap: any) { + const sourceMapPath = javascriptFilePath + '.map'; + const tsFilePath = changeExtension(javascriptFilePath, '.ts'); + + let sourceMapContent: string = null; + let typescriptFileContent: string = null; + + const readSourceMapPromise = readFileAsync(sourceMapPath); + readSourceMapPromise.then((content: string) => { + sourceMapContent = content; + }); + + const readTsFilePromise = readFileAsync(tsFilePath); + readTsFilePromise.then((content: string) => { + typescriptFileContent = content; + }); + + let promises: Promise[] = []; + promises.push(readSourceMapPromise); + promises.push(readTsFilePromise); + + Promise.all(promises) + .then(() => { + if (!sourceMapContent || !sourceMapContent.length) { + throw new Error('Failed to read sourcemap file'); + } else if (!typescriptFileContent || !typescriptFileContent.length) { + throw new Error('Failed to read typescript file'); + } else { + return JSON.parse(sourceMapContent); + } + }).then((sourceMapObject: any) => { + if (!sourceMapObject.sourcesContent || sourceMapObject.sourcesContent.length === 0) { + Logger.debug(`loadBetterSourceMap: Assigning Typescript content to source map for ${javascriptFilePath}`); + sourceMapObject.sourcesContent = [typescriptFileContent]; + } + callback(null, originalSource, sourceMapObject); + }).catch((err: Error) => { + Logger.debug(`Failed to generate typescript sourcemaps for ${javascriptFilePath}: ${err.message}`); + // just use the default + callback(null, originalSource, originalMap); + }); + +} diff --git a/src/loaders/typescript-sourcemap-loader-memory.ts b/src/loaders/typescript-sourcemap-loader-memory.ts new file mode 100644 index 00000000..0e15e88b --- /dev/null +++ b/src/loaders/typescript-sourcemap-loader-memory.ts @@ -0,0 +1,35 @@ +import { Logger } from '../util/logger'; +import { changeExtension, getContext, transformTmpPathToSrcPath} from '../util/helpers'; + +module.exports = function typescriptSourcemapLoaderMemory(source: string, map: string) { + this.cacheable(); + var callback = this.async(); + const context = getContext(); + const transformedPath = transformTmpPathToSrcPath(this.resourcePath, context); + const sourceMapPath = transformedPath + '.map'; + const sourceMapFile = context.fileCache.get(sourceMapPath); + + // get the typescript source if needed + let sourcesContent = source; + const isSrcFile = transformedPath !== this.resourcePath; + if (isSrcFile) { + Logger.debug('typescriptSourcemapLoaderMemory: attempting to use .ts instead of .js for sourcemap'); + const modifiedPath = changeExtension(transformedPath, '.ts'); + const typescriptFileObject = context.fileCache.get(modifiedPath); + if (typescriptFileObject) { + Logger.debug('typescriptSourcemapLoaderMemory: found the .ts file in memory, using it'); + sourcesContent = typescriptFileObject.content; + } + } + + if ( sourceMapFile ) { + const sourceMapObject = JSON.parse(sourceMapFile.content); + if (!sourceMapObject.sourcesContent || sourceMapObject.sourcesContent.length === 0) { + sourceMapObject.sourcesContent = [sourcesContent]; + } + callback(null, source, sourceMapObject); + } else { + callback(null, source, map); + } +}; + diff --git a/src/plugins/ion-compiler.ts b/src/plugins/ion-compiler.ts index 19228cd1..720babb2 100644 --- a/src/plugins/ion-compiler.ts +++ b/src/plugins/ion-compiler.ts @@ -1,4 +1,6 @@ +import { changeExtension } from '../util/helpers'; import { BuildContext } from '../util/interfaces'; +import { Logger } from '../util/logger'; import { dirname, join, resolve } from 'path'; import * as pluginutils from 'rollup-pluginutils'; @@ -6,6 +8,7 @@ import * as pluginutils from 'rollup-pluginutils'; export function ionCompiler(context: BuildContext) { const filter = pluginutils.createFilter(INCLUDE, EXCLUDE); + return { name: 'ion-compiler', @@ -14,16 +17,27 @@ export function ionCompiler(context: BuildContext) { return null; } - if (context.tsFiles) { - const file = context.tsFiles[sourcePath]; - if (!file || !file.output) { - console.error(`unable to find ${sourcePath}`); + const jsSourcePath = changeExtension(sourcePath, '.js'); + + if (context.fileCache) { + const file = context.fileCache.get(jsSourcePath); + const map = context.fileCache.get(jsSourcePath + '.map'); + if (!file || !file.content) { + Logger.debug(`transform: unable to find ${jsSourcePath}`); return null; } + let mapContent: any = null; + if (map.content) { + try { + mapContent = JSON.parse(map.content); + } catch (ex) { + } + } + return { - code: file.output, - map: file.map + code: file.content, + map: mapContent }; } @@ -35,10 +49,10 @@ export function ionCompiler(context: BuildContext) { }, load(sourcePath: string) { - if (context.tsFiles) { - const file = context.tsFiles[sourcePath]; - if (file && file.input) { - return file.input; + if (context.fileCache) { + const file = context.fileCache.get(sourcePath); + if (file && file.content) { + return file.content; } } @@ -55,26 +69,27 @@ export function resolveId(importee: string, importer: string, context: BuildCont return null; } - if (context.tsFiles) { - const importerFile = context.tsFiles[importer]; - if (importerFile && importerFile.output) { + if (context.fileCache) { + const importerFile = context.fileCache.get(importer); + if (importerFile && importerFile.content) { const attemptedImporteeBasename = resolve(join(dirname(importer), importee)); const attemptedImportee = attemptedImporteeBasename + '.ts'; - const importeeFile = context.tsFiles[attemptedImportee]; + const importeeFile = context.fileCache.get(attemptedImportee); if (importeeFile) { + Logger.debug(`resolveId: found and resolving ${attemptedImportee}`); return attemptedImportee; } else { // rather than a file, the attempedImportee could be a directory // while via node resolve pattern auto resolves to index file const attemptedImporteeIndex = resolve(join(attemptedImporteeBasename, 'index.ts')); - const importeeIndexFile = context.tsFiles[attemptedImporteeIndex]; + const importeeIndexFile = context.fileCache.get(attemptedImporteeIndex); if (importeeIndexFile) { + Logger.debug(`resolveId: found and resolving ${attemptedImporteeIndex}`); return attemptedImporteeIndex; } } } } - return null; } diff --git a/src/spec/ion-compiler.spec.ts b/src/spec/ion-compiler.spec.ts index 35d3c914..6f1232cb 100644 --- a/src/spec/ion-compiler.spec.ts +++ b/src/spec/ion-compiler.spec.ts @@ -1,10 +1,9 @@ -import { BuildContext } from '../util/interfaces'; +import { BuildContext, File } from '../util/interfaces'; import { dirname, join, resolve } from 'path'; import { resolveId } from '../plugins/ion-compiler'; const importer = '/Users/dan/Dev/ionic-conference-app/src/app/app.module.ts'; - describe('ion-compiler', () => { describe('resolveId', () => { @@ -22,7 +21,7 @@ describe('ion-compiler', () => { it('should return null when tsfiles is undefined/null', () => { // arrange let context: BuildContext = {}; - context.tsFiles = null; + context.fileCache = null; // act const result = resolveId('importee', importer, context); @@ -34,8 +33,8 @@ describe('ion-compiler', () => { it('should return null when importer is not found in list of files', () => { // arrange let context: BuildContext = {}; - context.tsFiles = { }; - context.tsFiles[importer] = null; + context.fileCache = new Map(); + context.fileCache.set(importer, null); // act const result = resolveId('importee', importer, context); @@ -47,8 +46,8 @@ describe('ion-compiler', () => { it('should return null when importer content lacks output property', () => { // arrange let context: BuildContext = {}; - context.tsFiles = { }; - context.tsFiles[importer] = { }; + context.fileCache = new Map(); + context.fileCache.set(importer, null); // act const result = resolveId('importee', importer, context); @@ -59,17 +58,21 @@ describe('ion-compiler', () => { it('should return path to file when file is found with ref to forward dir', () => { // arrange - let context: any = {}; - context.tsFiles = { }; - context.tsFiles[importer] = { - output: 'fake irrelevant data' - }; + let context: BuildContext = {}; + context.fileCache = new Map(); + context.fileCache.set(importer, { + path: importer, + content: 'fake irrelevant data' + }); const importee = './test-folder'; const importerBasename = dirname(importer); const importeeFullPath = resolve(join(importerBasename, importee)) + '.ts'; - context.tsFiles[importeeFullPath] = { }; + context.fileCache.set(importeeFullPath, { + path: importeeFullPath, + content: 'someContent' + }); // act const result = resolveId(importee, importer, context); @@ -81,17 +84,18 @@ describe('ion-compiler', () => { it('should return path to file when file is found with ref to backward dir', () => { // arrange - let context: any = {}; - context.tsFiles = { }; - context.tsFiles[importer] = { - output: 'fake irrelevant data' - }; + let context: BuildContext = {}; + context.fileCache = new Map(); + context.fileCache.set(importer, { + path: importer, + content: 'fake irrelevant data' + }); const importee = '../pages/test-folder'; const importerBasename = dirname(importer); const importeeFullPath = resolve(join(importerBasename, importee)) + '.ts'; - context.tsFiles[importeeFullPath] = { }; + context.fileCache.set(importeeFullPath, { path: importeeFullPath, content: null}); // act const result = resolveId(importee, importer, context); @@ -103,17 +107,18 @@ describe('ion-compiler', () => { it('should return path to index file when file is found but index file is for forward path', () => { // arrange - let context: any = {}; - context.tsFiles = { }; - context.tsFiles[importer] = { - output: 'fake irrelevant data' - }; + let context: BuildContext = {}; + context.fileCache = new Map(); + context.fileCache.set(importer, { + path: importer, + content: 'fake irrelevant data' + }); const importee = './test-folder'; const importerBasename = dirname(importer); const importeeFullPath = join(resolve(join(importerBasename, importee)), 'index.ts'); - context.tsFiles[importeeFullPath] = { }; + context.fileCache.set(importeeFullPath, { path: importeeFullPath, content: null }); // act const result = resolveId(importee, importer, context); @@ -125,17 +130,18 @@ describe('ion-compiler', () => { it('should return path to index file when file is found but index file is for backward path', () => { // arrange - let context: any = {}; - context.tsFiles = { }; - context.tsFiles[importer] = { - output: 'fake irrelevant data' - }; + let context: BuildContext = {}; + context.fileCache = new Map(); + context.fileCache.set(importer, { + path: importer, + content: 'fake irrelevant data' + }); const importee = '../pages/test-folder'; const importerBasename = dirname(importer); const importeeFullPath = join(resolve(join(importerBasename, importee)), 'index.ts'); - context.tsFiles[importeeFullPath] = { }; + context.fileCache.set(importeeFullPath, { path: importeeFullPath, content: null}); // act const result = resolveId(importee, importer, context); @@ -146,11 +152,12 @@ describe('ion-compiler', () => { it('should return null when importee isn\'t found in memory', () => { // arrange - let context: any = {}; - context.tsFiles = { }; - context.tsFiles[importer] = { - output: 'fake irrelevant data' - }; + let context: BuildContext = {}; + context.fileCache = new Map(); + context.fileCache.set(importer, { + path: importer, + content: 'fake irrelevant data' + }); const importee = '../pages/test-folder'; @@ -160,7 +167,5 @@ describe('ion-compiler', () => { // assert expect(result).toEqual(null); }); - }); - }); diff --git a/src/transpile.ts b/src/transpile.ts index 68359b9c..dbccd3cf 100644 --- a/src/transpile.ts +++ b/src/transpile.ts @@ -1,7 +1,7 @@ -import { BuildContext, TsFiles } from './util/interfaces'; +import { BuildContext, File } from './util/interfaces'; import { BuildError, Logger } from './util/logger'; import { buildJsSourceMaps } from './bundle'; -import { endsWith } from './util/helpers'; +import { changeExtension, endsWith } from './util/helpers'; import { generateContext } from './util/config'; import { inlineTemplate } from './template'; import { join, normalize, resolve } from 'path'; @@ -40,7 +40,7 @@ export function transpileUpdate(event: string, filePath: string, context: BuildC // however this ran, the changed file wasn't a .ts file // so if we already have tsFiles then make sure the context // has them and carry on - context.tsFiles = cachedTsFiles; + context.fileCache = cachedTsFiles; return Promise.resolve(); } @@ -70,7 +70,7 @@ export function transpileUpdate(event: string, filePath: string, context: BuildC export function transpileWorker(context: BuildContext, workerConfig: TranspileWorkerConfig) { // forget any tsFiles we've already cached if (workerConfig.writeInMemory) { - context.tsFiles = null; + context.fileCache = null; } // let's do this @@ -95,13 +95,15 @@ export function transpileWorker(context: BuildContext, workerConfig: TranspileWo tsConfig.options.declaration = undefined; // let's start a new tsFiles object to cache all the transpiled files in - const tsFiles: TsFiles = {}; const host = ts.createCompilerHost(tsConfig.options); + const cache = new Map(); + const program = ts.createProgram(tsFileNames, tsConfig.options, host, cachedProgram); - program.emit(undefined, (path: string, data: string) => { + program.emit(undefined, (path: string, data: string, writeByteOrderMark: boolean, onError: Function, sourceFiles: ts.SourceFile[]) => { if (workerConfig.writeInMemory) { - writeCallback(tsFiles, path, data, workerConfig.inlineTemplate); + writeSourceFiles(cache, sourceFiles); + writeTranspiledFilesCallback(cache, path, data, workerConfig.inlineTemplate); } }); @@ -122,11 +124,11 @@ export function transpileWorker(context: BuildContext, workerConfig: TranspileWo cachedProgram = program; if (workerConfig.writeInMemory) { - context.tsFiles = tsFiles; + context.fileCache = cache; } if (workerConfig.cache) { - cachedTsFiles = tsFiles; + cachedTsFiles = cache; } resolve(); @@ -146,7 +148,11 @@ function transpileUpdateWorker(event: string, filePath: string, context: BuildCo // processor core and don't let it's results hang anything up lintUpdate(event, filePath, context); - if (event === 'change' && context.tsFiles && context.tsFiles[filePath]) { + let file: File = null; + if (context.fileCache) { + file = context.fileCache.get(filePath); + } + if (event === 'change' && file) { try { // an existing ts file we already know about has changed // let's "TRY" to do a single module build for this one file @@ -181,32 +187,37 @@ function transpileUpdateWorker(event: string, filePath: string, context: BuildCo Logger.debug(`transpileUpdateWorker: transpileModule, missing output text`); } else { - const tsFile = context.tsFiles[filePath]; - if (tsFile) { - // success!! no need for a full rebuild!!! - tsFile.input = sourceText; - tsFile.map = transpileOutput.sourceMapText; - - if (workerConfig.inlineTemplate) { - tsFile.output = inlineTemplate(transpileOutput.outputText, filePath); - } - - // cool, the lil transpiling went through, but - // let's still do the big transpiling (on another processor core) - // and if there's anything wrong it'll print out messages - // however, it doesn't hang anything up - // also make sure it does a little as possible - const fullBuildWorkerConfig: TranspileWorkerConfig = { - configFile: workerConfig.configFile, - writeInMemory: false, - sourceMaps: false, - cache: false, - inlineTemplate: false - }; - runWorker('transpile', 'transpileWorker', context, fullBuildWorkerConfig); - - return Promise.resolve(); + // convert the path to have a .js file extension for consistency + const newPath = changeExtension(filePath, '.js'); + + const sourceMapFile = { path: newPath + '.map', content: transpileOutput.sourceMapText }; + let jsContent: string = transpileOutput.outputText; + if (workerConfig.inlineTemplate) { + // use original path for template inlining + jsContent = inlineTemplate(transpileOutput.outputText, filePath); } + const jsFile = { path: newPath, content: jsContent }; + const tsFile = { path: filePath, content: sourceText}; + + context.fileCache.set(sourceMapFile.path, sourceMapFile); + context.fileCache.set(jsFile.path, jsFile); + context.fileCache.set(tsFile.path, tsFile); + + // cool, the lil transpiling went through, but + // let's still do the big transpiling (on another processor core) + // and if there's anything wrong it'll print out messages + // however, it doesn't hang anything up + // also make sure it does a little as possible + const fullBuildWorkerConfig: TranspileWorkerConfig = { + configFile: workerConfig.configFile, + writeInMemory: false, + sourceMaps: false, + cache: false, + inlineTemplate: false + }; + runWorker('transpile', 'transpileWorker', context, fullBuildWorkerConfig); + + return Promise.resolve(); } } catch (e) { @@ -219,7 +230,7 @@ function transpileUpdateWorker(event: string, filePath: string, context: BuildCo // do a full build if it wasn't an existing file that changed // or we haven't transpiled the whole thing yet // or there were errors trying to transpile just the one module - Logger.debug(`transpileUpdateWorker: full build, context.tsFiles ${!!context.tsFiles}, event: ${event}, file: ${filePath}`); + Logger.debug(`transpileUpdateWorker: full build, context.tsFiles ${!!context.fileCache}, event: ${event}, file: ${filePath}`); return transpileWorker(context, workerConfig); } @@ -230,32 +241,41 @@ function cleanFileNames(context: BuildContext, fileNames: string[]) { return fileNames.filter(f => (f.indexOf(removeFileName) === -1)); } +function writeSourceFiles(fileCache: Map, sourceFiles: ts.SourceFile[]) { + for (const sourceFile of sourceFiles) { + fileCache.set(sourceFile.fileName, { path: sourceFile.fileName, content: sourceFile.text }); + } +} -function writeCallback(tsFiles: TsFiles, sourcePath: string, data: string, shouldInlineTemplate: boolean) { +function writeTranspiledFilesCallback(fileCache: Map, sourcePath: string, data: string, shouldInlineTemplate: boolean) { sourcePath = normalize(sourcePath); if (endsWith(sourcePath, '.js')) { - sourcePath = sourcePath.substring(0, sourcePath.length - 3) + '.ts'; + sourcePath = sourcePath.substring(0, sourcePath.length - 3) + '.js'; - let file = tsFiles[sourcePath]; + let file = fileCache.get(sourcePath); if (!file) { - file = tsFiles[sourcePath] = {}; + file = { content: '', path: sourcePath }; } if (shouldInlineTemplate) { - file.output = inlineTemplate(data, sourcePath); + file.content = inlineTemplate(data, sourcePath); } else { - file.output = data; + file.content = data; } + fileCache.set(sourcePath, file); + } else if (endsWith(sourcePath, '.js.map')) { - sourcePath = sourcePath.substring(0, sourcePath.length - 7) + '.ts'; - let file = tsFiles[sourcePath]; + sourcePath = sourcePath.substring(0, sourcePath.length - 7) + '.js.map'; + let file = fileCache.get(sourcePath); if (!file) { - file = tsFiles[sourcePath] = {}; + file = { content: '', path: sourcePath }; } - file.map = data; + file.content = data; + + fileCache.set(sourcePath, file); } } @@ -300,14 +320,12 @@ export function getTsConfig(context: BuildContext, tsConfigPath?: string): TsCon let cachedProgram: ts.Program = null; -let cachedTsFiles: TsFiles = null; - +let cachedTsFiles: Map = null; export function getTsConfigPath(context: BuildContext) { return join(context.rootDir, 'tsconfig.json'); } - export interface TsConfig { options: ts.CompilerOptions; fileNames: string[]; @@ -315,7 +333,6 @@ export interface TsConfig { raw: any; } - export interface TranspileWorkerConfig { configFile: string; writeInMemory: boolean; diff --git a/src/util/helpers.ts b/src/util/helpers.ts index 2d7add1b..8826ca29 100644 --- a/src/util/helpers.ts +++ b/src/util/helpers.ts @@ -1,8 +1,11 @@ +import { BuildContext } from './interfaces'; import { outputJson, readFile, readJsonSync, writeFile } from 'fs-extra'; import { BuildError, Logger } from './logger'; -import { join } from 'path'; +import { basename, dirname, extname, join } from 'path'; import { tmpdir } from 'os'; +let _context: BuildContext; + export const objectAssign = (Object.assign) ? Object.assign : function (target: any, source: any) { const output = Object(target); @@ -89,3 +92,27 @@ export function getModulePathsCache(): string[] { } return modulePaths; } + +export function setContext(context: BuildContext) { + _context = context; +} + +export function getContext() { + return _context; +} + +export function transformSrcPathToTmpPath(originalPath: string, context: BuildContext) { + return originalPath.replace(context.srcDir, context.tmpDir); +} + +export function transformTmpPathToSrcPath(originalPath: string, context: BuildContext) { + return originalPath.replace(context.tmpDir, context.srcDir); +} + +export function changeExtension(filePath: string, newExtension: string) { + const dir = dirname(filePath); + const extension = extname(filePath); + const extensionlessfileName = basename(filePath, extension); + const newFileName = extensionlessfileName + newExtension; + return join(dir, newFileName); +} \ No newline at end of file diff --git a/src/util/interfaces.ts b/src/util/interfaces.ts index fe46e19a..2a7b5fc3 100644 --- a/src/util/interfaces.ts +++ b/src/util/interfaces.ts @@ -16,7 +16,7 @@ export interface BuildContext { useTranspileCache?: boolean; useBundleCache?: boolean; useSassCache?: boolean; - tsFiles?: TsFiles; + fileCache?: Map; successfulSass?: boolean; inlineTemplates?: boolean; webpackWatch?: any; @@ -48,17 +48,6 @@ export interface TaskInfo { defaultConfigFile: string; } - -export interface TsFile { - input?: string; - output?: string; - map?: any; -} - -export interface TsFiles { - [sourcePath: string]: TsFile; -} - export interface File { path: string; content: string; diff --git a/src/watch.ts b/src/watch.ts index 95efaa8e..f35da815 100644 --- a/src/watch.ts +++ b/src/watch.ts @@ -89,9 +89,9 @@ function startWatcher(index: number, watcher: Watcher, context: BuildContext, wa function taskDone() { // TODO - why is this the way it is? - console.log(''); + Logger.newLine(); Logger.info(chalk.green.bold('watch ready')); - console.log(''); + Logger.newLine(); } const callbackToExecute = function(event: string, filePath: string, context: BuildContext, watcher: Watcher) { diff --git a/src/webpack.ts b/src/webpack.ts index f6b19789..001a1ba7 100644 --- a/src/webpack.ts +++ b/src/webpack.ts @@ -1,9 +1,9 @@ -import { BuildContext, File, TaskInfo, TsFiles } from './util/interfaces'; +import { BuildContext, File, TaskInfo } from './util/interfaces'; import { BuildError, IgnorableError, Logger } from './util/logger'; -import { readFileAsync, setModulePathsCache, writeFileAsync } from './util/helpers'; +import { changeExtension, readFileAsync, setContext, setModulePathsCache, transformSrcPathToTmpPath, writeFileAsync } from './util/helpers'; import { emit, EventType } from './util/events'; import { fillConfigDefaults, generateContext, getUserConfigFile, replacePathVars } from './util/config'; -import { basename, dirname, extname, join } from 'path'; +import { dirname, extname, join } from 'path'; import * as webpackApi from 'webpack'; import { mkdirs } from 'fs-extra'; @@ -28,6 +28,8 @@ export function webpack(context: BuildContext, configFile: string) { context = generateContext(context); configFile = getUserConfigFile(context, taskInfo, configFile); + Logger.debug('Webpack: Setting Context on shared singleton'); + setContext(context); const logger = new Logger('webpack'); return webpackWorker(context, configFile) @@ -48,7 +50,7 @@ export function webpackUpdate(event: string, path: string, context: BuildContext return Promise.resolve().then(() => { if (extension === '.ts') { Logger.debug('webpackUpdate: Typescript File Changed'); - return typescriptFileChanged(path, context.tsFiles); + return typescriptFileChanged(path, context.fileCache); } else { Logger.debug('webpackUpdate: Non-Typescript File Changed'); return otherFileChanged(path).then((file: File) => { @@ -59,7 +61,7 @@ export function webpackUpdate(event: string, path: string, context: BuildContext // transform the paths Logger.debug('webpackUpdate: Transforming paths'); const transformedPathFiles = files.map(file => { - file.path = transformPath(file.path, context); + file.path = transformSrcPathToTmpPath(file.path, context); return file; }); Logger.debug('webpackUpdate: Writing Files to tmp'); @@ -87,17 +89,8 @@ export function webpackUpdate(event: string, path: string, context: BuildContext export function webpackWorker(context: BuildContext, configFile: string): Promise { const webpackConfig = getWebpackConfig(context, configFile); - // in order to use watch mode, we need to write the - // transpiled files to disk, so go ahead and do that - let files = typescriptFilesChanged(context.tsFiles); - // transform the paths - const transformedPathFiles = files.map(file => { - file.path = transformPath(file.path, context); - return file; - }); - return writeFilesToDisk(transformedPathFiles) + return webpackWorkerPrepare(context) .then(() => { - Logger.debug('Wrote .js files to disk'); if (context.isWatch) { return runWebpackIncrementalBuild(!context.webpackWatch, context, webpackConfig); } else { @@ -108,6 +101,28 @@ export function webpackWorker(context: BuildContext, configFile: string): Promis }); } +function webpackWorkerPrepare(context: BuildContext) { + if (context.isProd) { + Logger.debug('webpackWorkerBefore: Prod build - skipping'); + return Promise.resolve(); + } else { + // in order to use watch mode, we need to write the + // transpiled files to disk, so go ahead and do that + let files: File[] = []; + context.fileCache.forEach(file => { + files.push(file); + }); + // transform the paths + const transformedPathFiles = files.map(file => { + file.path = transformSrcPathToTmpPath(file.path, context); + return file; + }); + return writeFilesToDisk(transformedPathFiles).then(() => { + Logger.debug('Wrote .js files to disk'); + }); + } +} + function webpackBuildComplete(stats: any, context: BuildContext, webpackConfig: WebpackConfig) { // set the module files used in this bundle // this reference can be used elsewhere in the build (sass) @@ -238,10 +253,6 @@ function writeIndividualFile(file: File) { }); } -function transformPath(originalPath: string, context: BuildContext) { - return originalPath.replace(context.srcDir, context.tmpDir); -} - function ensureDirectoriesExist(path: string) { return new Promise((resolve, reject) => { const directoryName = dirname(path); @@ -255,22 +266,11 @@ function ensureDirectoriesExist(path: string) { }); } -function typescriptFilesChanged(tsFiles: TsFiles) { - let files: File[] = []; - for (const filePath in tsFiles) { - const sourceAndMapFileArray = typescriptFileChanged(filePath, tsFiles); - for (const file of sourceAndMapFileArray) { - files.push(file); - } - } - return files; -} - -function typescriptFileChanged(fileChangedPath: string, tsFiles: TsFiles): File[] { - const fileName = basename(fileChangedPath, '.ts'); - const jsFilePath = join(dirname(fileChangedPath), fileName + '.js'); - const sourceFile = { path: jsFilePath, content: tsFiles[fileChangedPath].output }; - const mapFile = { path: jsFilePath + '.map', content: tsFiles[fileChangedPath].map}; +function typescriptFileChanged(fileChangedPath: string, fileCache: Map): File[] { + // convert to the .js file because those are the transpiled files in memory + const jsFilePath = changeExtension(fileChangedPath, '.js'); + const sourceFile = fileCache.get(jsFilePath); + const mapFile = fileCache.get(jsFilePath + '.map'); return [sourceFile, mapFile]; }