util.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. import { spawn } from 'cross-spawn';
  2. import { NODE_MODULES_ROOT, SRC_ROOT } from './constants';
  3. import { dest, src } from 'gulp';
  4. import { dirname, join, resolve } from 'path';
  5. import { ensureDirSync, readFile, readFileSync, readdirSync, statSync, writeFile, writeFileSync } from 'fs-extra';
  6. import { rollup } from 'rollup';
  7. import { Replacer } from 'strip-function';
  8. import * as commonjs from 'rollup-plugin-commonjs';
  9. import * as multiEntry from 'rollup-plugin-multi-entry';
  10. import * as nodeResolve from 'rollup-plugin-node-resolve';
  11. import * as through from 'through2';
  12. import * as uglifyPlugin from 'rollup-plugin-uglify';
  13. import { argv } from 'yargs';
  14. import { runWorker } from './utils/app-scripts-worker-client';
  15. // These packages lack of types.
  16. const resolveBin = require('resolve-bin');
  17. export function mergeObjects(obj1: any, obj2: any ) {
  18. if (! obj1) {
  19. obj1 = {};
  20. }
  21. if (! obj2) {
  22. obj2 = {};
  23. }
  24. var obj3 = {};
  25. for (let attrname in obj1) {
  26. (<any>obj3)[attrname] = obj1[attrname];
  27. }
  28. for (let attrname in obj2) {
  29. (<any>obj3)[attrname] = obj2[attrname];
  30. }
  31. return obj3;
  32. }
  33. function getRootTsConfig(pathToReadFile): any {
  34. const json = readFileSync(pathToReadFile);
  35. let tsConfig = JSON.parse(json.toString());
  36. return tsConfig;
  37. }
  38. export function createTempTsConfig(includeGlob: string[], target: string, moduleType: string, pathToReadFile: string, pathToWriteFile: string, overrideCompileOptions: any = null): any {
  39. let config = getRootTsConfig(pathToReadFile);
  40. if (!config.compilerOptions) {
  41. config.compilerOptions = {};
  42. }
  43. // for now, we only compiling to same directory (no outdir)
  44. if (config.compilerOptions && config.compilerOptions.outDir) {
  45. delete config.compilerOptions.outDir;
  46. }
  47. // remove linting checks that we do not want in dist
  48. if (config.compilerOptions.noUnusedLocals) {
  49. delete config.compilerOptions.noUnusedLocals;
  50. }
  51. if (config.compilerOptions.noUnusedParameters) {
  52. delete config.compilerOptions.noUnusedParameters;
  53. }
  54. if (config.compilerOptions) {
  55. config.compilerOptions.module = moduleType;
  56. config.compilerOptions.target = target;
  57. }
  58. config.include = includeGlob;
  59. if (overrideCompileOptions) {
  60. config.compilerOptions = Object.assign(config.compilerOptions, overrideCompileOptions);
  61. }
  62. // TS represents paths internally with '/' and expects the tsconfig path to be in this format
  63. let json = JSON.stringify(config, null, 2);
  64. json = json.replace(/\\\\/g, '/');
  65. const dirToCreate = dirname(pathToWriteFile);
  66. ensureDirSync(dirToCreate);
  67. writeFileSync(pathToWriteFile, json);
  68. }
  69. function removeDebugStatements() {
  70. let replacer = new Replacer(['console.debug', 'console.time', 'console.timeEnd', 'assert', 'runInDev']);
  71. return through.obj(function (file, encoding, callback) {
  72. const content = file.contents.toString();
  73. const cleanedJs = replacer.replace(content);
  74. file.contents = new Buffer(cleanedJs, 'utf8');
  75. callback(null, file);
  76. });
  77. }
  78. export function copySourceToDest(destinationPath: string, excludeSpecs: boolean, excludeE2e: boolean, stripDebug: boolean) {
  79. let glob = [`${SRC_ROOT}/**/*.ts`];
  80. if (excludeSpecs) {
  81. glob.push(`!${SRC_ROOT}/**/*.spec.ts`);
  82. } else {
  83. glob.push(`${SRC_ROOT}/**/*.spec.ts`);
  84. }
  85. if (excludeE2e) {
  86. glob.push(`!${SRC_ROOT}/components/*/test/*/**/*.ts`);
  87. }
  88. let stream = src(glob);
  89. if (stripDebug) {
  90. console.log('Removing debug statements:', destinationPath);
  91. stream = stream.pipe(removeDebugStatements());
  92. }
  93. return stream.pipe(dest(destinationPath));
  94. }
  95. export function copyGlobToDest(sourceGlob: string[], destPath: string) {
  96. return src(sourceGlob).pipe(dest(destPath));
  97. }
  98. export function copyFonts(destinationPath: string) {
  99. return src([
  100. 'src/fonts/*.+(ttf|woff|woff2)',
  101. 'node_modules/ionicons/dist/fonts/*.+(ttf|woff|woff2)'
  102. ])
  103. .pipe(dest(destinationPath));
  104. }
  105. export function compileSass(destinationPath: string) {
  106. let sass = require('gulp-sass');
  107. let autoprefixer = require('gulp-autoprefixer');
  108. let cleanCSS = require('gulp-clean-css');
  109. let rename = require('gulp-rename');
  110. let buildConfig = require('../build/config');
  111. let ioniconsPath = join(NODE_MODULES_ROOT, 'ionicons/dist/scss/');
  112. return src([
  113. join(SRC_ROOT, 'themes/ionic.build.default.scss'),
  114. join(SRC_ROOT, 'themes/ionic.build.dark.scss')
  115. ])
  116. .pipe(sass({
  117. includePaths: [ioniconsPath]
  118. }).on('error', sass.logError)
  119. )
  120. .pipe(autoprefixer(buildConfig.autoprefixer))
  121. .pipe(rename(function (path) {
  122. path.basename = path.basename.replace('.default', '');
  123. path.basename = path.basename.replace('.build', '');
  124. }))
  125. .pipe(dest(destinationPath))
  126. .pipe(cleanCSS())
  127. .pipe(rename({
  128. extname: '.min.css'
  129. }))
  130. .pipe(dest(destinationPath));
  131. }
  132. export function setSassIonicVersion(version: string) {
  133. writeFileSync(join(SRC_ROOT, 'themes/version.scss'), `$ionic-version: "${version}";`);
  134. }
  135. export function copyFile(srcPath: string, destPath: string) {
  136. const sourceData = readFileSync(srcPath);
  137. writeFileSync(destPath, sourceData);
  138. }
  139. export function runNgc(pathToConfigFile: string, done: Function) {
  140. let exec = require('child_process').exec;
  141. let ngcPath = getBinaryPath('@angular/compiler-cli', 'ngc');
  142. let shellCommand = `node --max_old_space_size=8096 ${ngcPath} -p ${pathToConfigFile}`;
  143. exec(shellCommand, function(err, stdout, stderr) {
  144. process.stdout.write(stdout);
  145. process.stderr.write(stderr);
  146. done(err);
  147. });
  148. }
  149. export function runTsc(pathToConfigFile: string, done: Function) {
  150. let exec = require('child_process').exec;
  151. let tscPath = getBinaryPath('typescript', 'tsc');
  152. let shellCommand = `node --max_old_space_size=8096 ${tscPath} -p ${pathToConfigFile}`;
  153. exec(shellCommand, function (err, stdout, stderr) {
  154. process.stdout.write(stdout);
  155. process.stderr.write(stderr);
  156. done(err);
  157. });
  158. }
  159. export function runWebpack(pathToWebpackConfig: string, done: Function) {
  160. let exec = require('child_process').exec;
  161. let webpackPath = getBinaryPath('webpack');
  162. let shellCommand = `node --max_old_space_size=8096 ${webpackPath} --config ${pathToWebpackConfig} --display-error-details`;
  163. exec(shellCommand, function(err, stdout, stderr) {
  164. process.stdout.write(stdout);
  165. process.stderr.write(stderr);
  166. done(err);
  167. });
  168. }
  169. export function runAppScriptsServe(testOrDemoName: string, appEntryPoint: string, appNgModulePath: string, srcDir: string, distDir: string, tsConfig: string, ionicAngularDir: string, sassConfigPath: string, copyConfigPath: string, watchConfigPath: string, devApp: boolean) {
  170. console.log('Running ionic-app-scripts serve with', testOrDemoName);
  171. const deepLinksDir = dirname(dirname(appNgModulePath));
  172. let scriptArgs = [
  173. 'serve',
  174. '--appEntryPoint', appEntryPoint,
  175. '--appNgModulePath', appNgModulePath,
  176. '--deepLinksDir', deepLinksDir,
  177. '--srcDir', srcDir,
  178. '--wwwDir', distDir,
  179. '--tsconfig', tsConfig,
  180. '--readConfigJson', 'false',
  181. '--ionicAngularDir', ionicAngularDir,
  182. '--sass', sassConfigPath,
  183. '--copy', copyConfigPath,
  184. '--enableLint', 'false',
  185. '--skipIonicAngularVersion', 'true'
  186. ];
  187. if (devApp) {
  188. scriptArgs.push('--bonjour');
  189. }
  190. if (watchConfigPath) {
  191. scriptArgs.push('--watch');
  192. scriptArgs.push(watchConfigPath);
  193. }
  194. const debug: boolean = argv.debug;
  195. if (debug) {
  196. scriptArgs.push('--debug');
  197. }
  198. return new Promise((resolve, reject) => {
  199. let pathToAppScripts = join(NODE_MODULES_ROOT, '.bin', 'ionic-app-scripts');
  200. pathToAppScripts = process.platform === 'win32' ? pathToAppScripts + '.cmd' : pathToAppScripts;
  201. const spawnedCommand = spawn(pathToAppScripts, scriptArgs, {stdio: 'inherit'});
  202. console.log(`${pathToAppScripts} ${scriptArgs.join(' ')}`);
  203. spawnedCommand.on('close', (code: number) => {
  204. if (code === 0) {
  205. return resolve();
  206. }
  207. reject(new Error('App-scripts failed with non-zero status code'));
  208. });
  209. });
  210. }
  211. export function runAppScriptsBuild(appEntryPoint: string, appNgModulePath: string, srcDir: string, distDir: string, tsConfig: string, ionicAngularDir: string, sassConfigPath: string, copyConfigPath: string, isDev: boolean = false, minifyCss: boolean = true, minifyJs: boolean = true, optimizeJs: boolean = true) {
  212. const pathToAppScripts = join(NODE_MODULES_ROOT, '.bin', 'ionic-app-scripts');
  213. const debug: boolean = argv.debug;
  214. return runWorker(pathToAppScripts, debug, appEntryPoint, appNgModulePath, srcDir, distDir, tsConfig, ionicAngularDir, sassConfigPath, copyConfigPath, isDev, minifyCss, minifyJs, optimizeJs);
  215. }
  216. /** Resolves the path for a node package executable. */
  217. export function getBinaryPath(packageName: string, executable = packageName): string {
  218. return resolveBin.sync(packageName, {executable});
  219. }
  220. export function deleteFiles(glob: string[], done: Function) {
  221. let del = require('del');
  222. del.sync(glob);
  223. done();
  224. }
  225. export function createTimestamp() {
  226. // YYYYMMDDHHMM
  227. var d = new Date();
  228. return d.getUTCFullYear() + // YYYY
  229. ('0' + (d.getUTCMonth() + 1)).slice(-2) + // MM
  230. ('0' + (d.getUTCDate())).slice(-2) + // DD
  231. ('0' + (d.getUTCHours())).slice(-2) + // HH
  232. ('0' + (d.getUTCMinutes())).slice(-2); // MM
  233. }
  234. export function writePolyfills(outputDirectory: string) {
  235. const MODERN_ENTRIES = [
  236. 'node_modules/core-js/es6/array.js',
  237. 'node_modules/core-js/es6/date.js',
  238. 'node_modules/core-js/es6/function.js',
  239. 'node_modules/core-js/es6/map.js',
  240. 'node_modules/core-js/es6/number.js',
  241. 'node_modules/core-js/es6/object.js',
  242. 'node_modules/core-js/es6/parse-float.js',
  243. 'node_modules/core-js/es6/parse-int.js',
  244. 'node_modules/core-js/es6/set.js',
  245. 'node_modules/core-js/es6/string.js',
  246. 'node_modules/core-js/es7/reflect.js',
  247. 'node_modules/core-js/es6/reflect.js',
  248. 'node_modules/zone.js/dist/zone.js',
  249. 'scripts/polyfill/polyfill.dom.js'
  250. ];
  251. const ALL_ENTRIES = [
  252. 'node_modules/core-js/es6/array.js',
  253. 'node_modules/core-js/es6/date.js',
  254. 'node_modules/core-js/es6/function.js',
  255. 'node_modules/core-js/es6/map.js',
  256. 'node_modules/core-js/es6/math.js',
  257. 'node_modules/core-js/es6/number.js',
  258. 'node_modules/core-js/es6/object.js',
  259. 'node_modules/core-js/es6/parse-float.js',
  260. 'node_modules/core-js/es6/parse-int.js',
  261. 'node_modules/core-js/es6/reflect.js',
  262. 'node_modules/core-js/es6/regexp.js',
  263. 'node_modules/core-js/es6/set.js',
  264. 'node_modules/core-js/es6/string.js',
  265. 'node_modules/core-js/es6/symbol.js',
  266. 'node_modules/core-js/es6/typed.js',
  267. 'node_modules/core-js/es6/weak-map.js',
  268. 'node_modules/core-js/es6/weak-set.js',
  269. 'node_modules/core-js/es7/reflect.js',
  270. 'node_modules/zone.js/dist/zone.js',
  271. 'scripts/polyfill/polyfill.dom.js'
  272. ];
  273. const NG_ENTRIES = [
  274. 'node_modules/core-js/es7/reflect.js',
  275. 'node_modules/zone.js/dist/zone.js',
  276. ];
  277. let promises = [];
  278. promises.push(bundlePolyfill(MODERN_ENTRIES, join(outputDirectory, 'polyfills.modern.js')));
  279. promises.push(bundlePolyfill(ALL_ENTRIES, join(outputDirectory, 'polyfills.js')));
  280. promises.push(bundlePolyfill(NG_ENTRIES, join(outputDirectory, 'polyfills.ng.js')));
  281. return Promise.all(promises);
  282. }
  283. function bundlePolyfill(pathsToIncludeInPolyfill: string[], outputPath: string) {
  284. return rollup({
  285. entry: pathsToIncludeInPolyfill,
  286. plugins: [
  287. multiEntry(),
  288. nodeResolve({
  289. module: true,
  290. jsnext: true,
  291. main: true
  292. }),
  293. commonjs(),
  294. uglifyPlugin()
  295. ],
  296. onwarn: () => {
  297. return () => {};
  298. }
  299. }).then((bundle) => {
  300. return bundle.write({
  301. format: 'iife',
  302. moduleName: 'MyBundle',
  303. dest: outputPath
  304. });
  305. }).catch(err => {
  306. console.log('caught rollup error: ', err);
  307. });
  308. }
  309. export function getFolderInfo() {
  310. let componentName: string = null;
  311. let componentTest: string = null;
  312. const folder: string = argv.folder || argv.f;
  313. if (folder && folder.length) {
  314. const folderSplit = folder.split('/');
  315. componentName = folderSplit[0];
  316. componentTest = (folderSplit.length > 1 ? folderSplit[1] : 'basic');
  317. }
  318. const devApp = argv.devapp !== undefined;
  319. return {
  320. componentName: componentName,
  321. componentTest: componentTest,
  322. devApp: devApp
  323. };
  324. }
  325. export function getFolders(dir) {
  326. return readdirSync(dir)
  327. .filter(function(file) {
  328. return statSync(join(dir, file)).isDirectory();
  329. });
  330. }
  331. export function readFileAsync(filePath: string) {
  332. return new Promise((resolve, reject) => {
  333. readFile(filePath, (err: Error, buffer: Buffer) => {
  334. if (err) {
  335. return reject(err);
  336. }
  337. return resolve(buffer.toString());
  338. });
  339. });
  340. }
  341. export function writeFileAsync(filePath: string, fileContent: string) {
  342. return new Promise((resolve, reject) => {
  343. writeFile(filePath, fileContent, (err: Error) => {
  344. if (err) {
  345. return reject(err);
  346. }
  347. return resolve();
  348. });
  349. });
  350. }