UNPKG

cyb-cli

Version:

塞伯坦CYB-CLI是面向前端模块化工程的构建工具。主要目的是帮助开发者统一前端开发模式和项目开发结构,提高功能扩展和降低维护成本,自动化前端工作流,提高开发效率和开发质量。

1,066 lines (988 loc) 29.3 kB
/** * ================================= * @2018 塞伯坦-CYB前端模块化工程构建工具 * https://github.com/jd-cyb/cyb-cli * ================================= */ /** * --------------------------------- * 生产任务 * --------------------------------- */ const path = require('path') const injectString = require('gulp-inject-string') const fs = require('fs') const chalk = require('chalk') const mainBowerFiles = require('main-bower-files') const filter = require('gulp-filter') const flatten = require('gulp-flatten') const inject = require('gulp-inject') const lazypipe = require('lazypipe') const es = require('event-stream') const rename = require('gulp-rename') const del = require('del') const gulpif = require('gulp-if') const less = require('gulp-less') const sass = require('gulp-sass') const stylus = require('gulp-stylus') const postcss = require('gulp-postcss') const autoprefixer = require('autoprefixer') const postcssPxtorem = require('postcss-pxtorem') const uglify = require('gulp-uglify') const htmlmin = require('gulp-htmlmin') const usemin = require('gulp-usemin') const minifyCSS = require('gulp-clean-css') const RevAll = require('gulp-rev') const RevReplace = require('gulp-rev-replace') const revFormat = require('gulp-rev-format'); const revDel = require('gulp-rev-delete-original') const concatJs = require('gulp-concat') const concatCss = require('gulp-concat-css') const concatOrder = require("gulp-fez-order") const cdnify = require('gulp-fez-cdn') const vfs = require('vinyl-fs') const async = require('async') const glob = require('glob') const ora = require('ora') const distReport = require('./lib/report') const config = require('./lib/fezconfig') const webp = require('./lib/webp') const compileJs = require('./lib/webpack') module.exports = () => { let startTime = new Date const spinner = ora(chalk.cyan('Starting dist for production...')).start() /** * postcss配置 */ const postcssOption = [autoprefixer(config.autoprefixer.options)] /** * 检测bower是否可用 */ const bowerAvailable = () => { if (!config.merge.vendor.available) return false if (!fs.existsSync(path.join(process.cwd(), 'bower_components'))) return false if (mainBowerFiles().length <= 0) return false return true } /** * 清除 dist 目录 **/ function delDist(cb) { del(config.paths.dist.dir) .then(() => { cb() }) } /** * 处理图片 **/ function handleImages(cb) { const imagesPath = path.join(process.cwd(), config.paths.src.img) const exist = fs.existsSync(imagesPath) if (!exist) return cb() vfs.src(`${imagesPath}/**/*`) .pipe(vfs.dest(config.paths.tmp.img)) .pipe(RevAll()) .pipe(revFormat({ prefix: '.' })) .pipe(revDel()) .pipe(vfs.dest(config.paths.tmp.img)) .pipe(RevAll.manifest(path.join(config.paths.tmp.img, 'rev-manifest.json'), { base: config.paths.tmp.img, merge: true })) .pipe(vfs.dest(config.paths.tmp.img)) .on('end', () => { cb() }) } /** * 处理字体 **/ function handleFonts(cb) { const fontPath = path.join(process.cwd(), config.paths.src.fonts) const exist = fs.existsSync(fontPath) if (!exist) return cb() vfs.src(`${fontPath}/**/*.{otf,eot,svg,ttf,woff,woff2}`) .pipe(vfs.dest(config.paths.tmp.fonts)) .pipe(RevAll()) .pipe(revFormat({ prefix: '.' })) .pipe(revDel()) .pipe(vfs.dest(config.paths.tmp.fonts)) .pipe(RevAll.manifest(path.join(config.paths.tmp.fonts, 'rev-manifest.json'), { base: config.paths.tmp.fonts, merge: true })) .pipe(vfs.dest(config.paths.tmp.fonts)) .on('end', () => { cb() }) } /** * 处理自定义文件 **/ function handleCustom(cb) { const customPath = path.join(process.cwd(), config.paths.src.custom) const exist = fs.existsSync(customPath) if (!exist) return cb() /** * 自定义custom目录不做revision */ vfs.src(`${customPath}/**/*`) .pipe(vfs.dest(config.paths.tmp.custom)) .on('end', () => { cb() }) } /** * 编译css **/ function compileCss(cb) { if (glob.sync(`${config.paths.src.styles}/*.css`).length === 0) return cb() let manifest = vfs.src(`${config.paths.tmp.dir}/**/rev-manifest.json`) vfs.src(`${config.paths.src.styles}/*.css`) //css中的rem转换 .pipe(gulpif( config.useREM.css.available, postcss([ postcssPxtorem(Object.assign({}, config.useREM.css.options)) ]) )) .pipe(postcss(postcssOption)) .pipe(gulpif( config.useCssMin.available, minifyCSS(config.useCssMin.options) )) .pipe(RevReplace({ manifest: manifest })) .pipe(cdnify({ base: '', rewriter: (url, process) => { if (/(^(\/)?static)\/(.*?)\.(png|jpe?g|gif|svg|mp4|webm|ogg|mp3|wav|flac|aac|woff2?|eot|ttf|otf)/.test(url)) { url = url.replace(/((\/)?static\/)/, '../') return url } else { return process(url) } } })) .pipe(vfs.dest(config.paths.tmp.css)) .pipe(RevAll()) .pipe(revFormat({ prefix: '.' })) .pipe(revDel()) .pipe(vfs.dest(config.paths.tmp.css)) .pipe(RevAll.manifest(path.join(config.paths.tmp.css, 'rev-manifest.json'), { base: config.paths.tmp.css, merge: true })) .pipe(vfs.dest(config.paths.tmp.css)) .on('end', () => { cb() }) } /** * 编译less **/ function compileLess(cb) { if (glob.sync(`${config.paths.src.styles}/*.less`).length === 0) return cb() let manifest = vfs.src(`${config.paths.tmp.dir}/**/rev-manifest.json`) vfs.src(`${config.paths.src.styles}/*.less`) .pipe(less(Object.assign({ relativeUrls: true //将网址编译成相对网址 }, config.style.lessOptions))) //css中的rem转换 .pipe(gulpif( config.useREM.css.available, postcss([ postcssPxtorem(Object.assign({}, config.useREM.css.options)) ]) )) .pipe(postcss(postcssOption)) .pipe(gulpif( config.useCssMin.available, minifyCSS(config.useCssMin.options) )) .pipe(RevReplace({ manifest: manifest })) .pipe(cdnify({ base: '', rewriter: (url, process) => { if (/(^(\/)?static)\/(.*?)\.(png|jpe?g|gif|svg|mp4|webm|ogg|mp3|wav|flac|aac|woff2?|eot|ttf|otf)/.test(url)) { url = url.replace(/((\/)?static\/)/, '../') return url } else { return process(url) } } })) .pipe(vfs.dest(config.paths.tmp.css)) .pipe(RevAll()) .pipe(revFormat({ prefix: '.' })) .pipe(revDel()) .pipe(vfs.dest(config.paths.tmp.css)) .pipe(RevAll.manifest(path.join(config.paths.tmp.css, 'rev-manifest.json'), { base: config.paths.tmp.css, merge: true })) .pipe(vfs.dest(config.paths.tmp.css)) .on('end', () => { cb() }) } /** * 编译sass **/ function compileSass(cb) { if (glob.sync(`${config.paths.src.styles}/*.{scss,sass}`).length === 0) return cb() let manifest = vfs.src(`${config.paths.tmp.dir}/**/rev-manifest.json`) vfs.src(`${config.paths.src.styles}/*.{scss,sass}`) .pipe(sass(Object.assign({ /** * ------- outputStyle 取值 ------ * nested:嵌套缩进的css代码,它是默认值。 * expanded:没有缩进的、扩展的css代码。 * compact:简洁格式的css代码。 * compressed:压缩后的css代码 */ outputStyle: 'compact' }, config.style.sassOptions))) //css中的rem转换 .pipe(gulpif( config.useREM.css.available, postcss([ postcssPxtorem(Object.assign({}, config.useREM.css.options)) ]) )) .pipe(postcss(postcssOption)) .pipe(gulpif( config.useCssMin.available, minifyCSS(config.useCssMin.options) )) .pipe(RevReplace({ manifest: manifest })) .pipe(cdnify({ base: '', rewriter: (url, process) => { if (/(^(\/)?static)\/(.*?)\.(png|jpe?g|gif|svg|mp4|webm|ogg|mp3|wav|flac|aac|woff2?|eot|ttf|otf)/.test(url)) { url = url.replace(/((\/)?static\/)/, '../') return url } else { return process(url) } } })) .pipe(vfs.dest(config.paths.tmp.css)) .pipe(RevAll()) .pipe(revFormat({ prefix: '.' })) .pipe(revDel()) .pipe(vfs.dest(config.paths.tmp.css)) .pipe(RevAll.manifest(path.join(config.paths.tmp.css, 'rev-manifest.json'), { base: config.paths.tmp.css, merge: true })) .pipe(vfs.dest(config.paths.tmp.css)) .on('end', () => { cb() }) } /** * 编译stylus **/ function compileStylus(cb) { if (glob.sync(`${config.paths.src.styles}/*.styl`).length === 0) return cb() let manifest = vfs.src(`${config.paths.tmp.dir}/**/rev-manifest.json`) vfs.src(`${config.paths.src.styles}/*.styl`) .pipe(stylus(Object.assign({}, config.style.stylusOptions))) //css中的rem转换 .pipe(gulpif( config.useREM.css.available, postcss([ postcssPxtorem(Object.assign({}, config.useREM.css.options)) ]) )) .pipe(postcss(postcssOption)) .pipe(gulpif( config.useCssMin.available, minifyCSS(config.useCssMin.options) )) .pipe(RevReplace({ manifest: manifest })) .pipe(cdnify({ base: '', rewriter: (url, process) => { if (/(^(\/)?static)\/(.*?)\.(png|jpe?g|gif|svg|mp4|webm|ogg|mp3|wav|flac|aac|woff2?|eot|ttf|otf)/.test(url)) { url = url.replace(/((\/)?static\/)/, '../') return url } else { return process(url) } } })) .pipe(vfs.dest(config.paths.tmp.css)) .pipe(RevAll()) .pipe(revFormat({ prefix: '.' })) .pipe(revDel()) .pipe(vfs.dest(config.paths.tmp.css)) .pipe(RevAll.manifest(path.join(config.paths.tmp.css, 'rev-manifest.json'), { base: config.paths.tmp.css, merge: true })) .pipe(vfs.dest(config.paths.tmp.css)) .on('end', () => { cb() }) } /** * 编译业务层js **/ function handleJs(cb) { spinner.stop() console.log(chalk.yellow('Building javascript with webpack...')) compileJs.dist() .then(() => { // spinner.stop() let manifest = vfs.src(`${config.paths.tmp.dir}/**/rev-manifest.json`) vfs.src(`${config.paths.tmp.appjs}/**/*`) .pipe(RevReplace({ manifest: manifest })) .pipe(vfs.dest(config.paths.tmp.appjs)) .on('end', () => { spinner.start(chalk.cyan('Starting dist for production...')) cb() }) }) .catch((error) => { console.log(error) }) } /** * 复制bower文件到缓存目录等待处理 **/ function copyBowerFiles(cb) { if (!bowerAvailable()) return cb() const jsFilter = filter('**/*.js', { restore: true }) const cssFilter = filter('**/*.css', { restore: true }) const fontFilter = filter('**/*.{eot,svg,ttf,woff,woff2}') vfs.src(mainBowerFiles()) .pipe(jsFilter) .pipe(vfs.dest(`./tmp/bower/js`)) .pipe(jsFilter.restore) .pipe(cssFilter) //Bower 中的 css rem 转换 .pipe(gulpif( config.useREM.css.available, postcss([ postcssPxtorem({}) ]) )) .pipe(vfs.dest(`./tmp/bower/css`)) .pipe(cssFilter.restore) .pipe(fontFilter) .pipe(flatten()) .pipe(vfs.dest(config.paths.tmp.fonts)) .pipe(RevAll()) .pipe(revFormat({ prefix: '.' })) .pipe(revDel()) .pipe(vfs.dest(config.paths.tmp.fonts)) .pipe(RevAll.manifest(path.join(config.paths.tmp.fonts, 'rev-manifest.json'), { base: config.paths.tmp.fonts, merge: true })) .pipe(vfs.dest(config.paths.tmp.fonts)) .on('end', () => { cb() }) } function bowerCustomJs(cb) { if (!bowerAvailable() || config.merge.vendor.js.length === 0) return cb() let fileIndex = 0 for (let [index, elem] of config.merge.vendor.js.entries()) { vfs.src('./tmp/bower/**/*.js') .pipe(filter(elem.contain)) .pipe(concatOrder(elem.contain)) .pipe(concatJs(`vendor-${index}-bower-${elem.target}`)) .pipe(uglify()) .pipe(vfs.dest(config.paths.tmp.appjs)) .on("end", () => { fileIndex++ let delFiles = [] for (let item of elem.contain) { delFiles.push(`./tmp/bower/${item}`) } del.sync(delFiles) if (fileIndex >= config.merge.vendor.js.length) { cb() } }) } } function bowerVendorJs(cb) { if (!bowerAvailable()) return cb() vfs.src('./tmp/bower/**/*.js') .pipe(filter('**/*.js')) .pipe(concatJs('vendor-bower.js')) .pipe(uglify()) .pipe(vfs.dest(config.paths.tmp.appjs)) .on('end', () => { cb() }) } function bowerCustomCss(cb) { if (!bowerAvailable() || config.merge.vendor.css.length === 0) return cb() let fileIndex = 0 for (let [index, elem] of config.merge.vendor.css.entries()) { vfs.src(`./tmp/bower/**/*.css`) .pipe(filter(elem.contain)) .pipe(concatOrder(elem.contain)) .pipe(concatCss(`vendor-${index}-bower-${elem.target}`, { rebaseUrls: false })) .pipe(gulpif( config.useCssMin.available, minifyCSS() )) .pipe(flatten()) .pipe(vfs.dest(config.paths.tmp.css)) .on("end", () => { fileIndex++ let delFiles = [] for (let item of elem.contain) { delFiles.push(`./tmp/bower/${item}`) } del.sync(delFiles) if (fileIndex >= config.merge.vendor.css.length) { cb() } }) } } function bowerVendorCss(cb) { if (!bowerAvailable()) return cb() vfs.src('./tmp/bower/**/*.css') .pipe(filter('**/*.css')) .pipe(concatCss('vendor-bower.css', { rebaseUrls: false })) .pipe(gulpif( config.useCssMin.available, minifyCSS() )) .pipe(flatten()) .pipe(vfs.dest(config.paths.tmp.css)) .on("end", () => { del.sync(['./tmp/bower']) cb() }) } /** * 复制公共脚本到缓存目录等待处理 **/ function copyCommonFiles(cb) { if (!fs.existsSync(path.join(process.cwd(), config.paths.src.common))) return cb() vfs.src(`${config.paths.src.common}/**/*.js`) .pipe(vfs.dest(config.paths.tmp.common)) .on('end', () => { cb() }) } /** * 根据 fez.config.js 配置项合并公共脚本 **/ function commonCustomJs(cb) { if (!fs.existsSync(path.join(process.cwd(), config.paths.src.common)) || !config.merge.common.available || config.merge.common.js.length === 0) return cb() let fileIndex = 0 for (let [index, elem] of config.merge.common.js.entries()) { vfs.src(`${config.paths.tmp.common}/**/*.js`) .pipe(filter(elem.contain)) .pipe(concatOrder(elem.contain)) .pipe(concatJs(`common-${index}-browser-${elem.target}`)) .pipe(uglify()) .pipe(vfs.dest(config.paths.tmp.appjs)) .on("end", () => { fileIndex++ let delFiles = [] for (let item of elem.contain) { delFiles.push(path.join(config.paths.tmp.common, `${item}*`)) } del.sync(delFiles) if (fileIndex >= config.merge.common.js.length) { cb() } }) } } /** * 合并 fez.config.js 中未配置的剩下的所有公共脚本 **/ function commonJs(cb) { if (!fs.existsSync(path.join(process.cwd(), config.paths.src.common))) return cb() vfs.src(`${config.paths.tmp.common}**/*.js`) .pipe(filter('**/*.js')) .pipe(concatJs('common-browser.js')) .pipe(uglify()) .pipe(vfs.dest(config.paths.tmp.appjs)) .on("end", () => { del.sync([config.paths.tmp.common]) cb() }) } /** * 为bower和common统一添加版本号 **/ function reversionBowerCommonJs(cb) { let manifest = vfs.src(`${config.paths.tmp.dir}/**/rev-manifest.json`) vfs.src(`${config.paths.tmp.appjs}/**/*.js`) .pipe(RevReplace({ manifest: manifest })) .pipe(RevAll()) .pipe(revFormat({ prefix: '.' })) .pipe(revDel()) .pipe(vfs.dest(config.paths.tmp.appjs)) .pipe(RevAll.manifest(path.join(config.paths.tmp.appjs, 'rev-manifest.json'), { base: config.paths.tmp.appjs, merge: true })) .pipe(vfs.dest(config.paths.tmp.appjs)) .on('end', () => { cb() }) } /** * 为bower和common统一添加版本号 **/ function reversionBowerCommonCss(cb) { let manifest = vfs.src(`${config.paths.tmp.dir}/**/rev-manifest.json`) vfs.src(`${config.paths.tmp.css}/**/*.css`) .pipe(RevReplace({ manifest: manifest })) .pipe(cdnify({ base: '', rewriter: (url, process) => { if (/(^(\/)?static)\/(.*?)\.(png|jpe?g|gif|svg|mp4|webm|ogg|mp3|wav|flac|aac|woff2?|eot|ttf|otf)/.test(url)) { url = url.replace(/((\/)?static\/)/, '../') return url } else { return process(url) } } })) .pipe(RevAll()) .pipe(revFormat({ prefix: '.' })) .pipe(revDel()) .pipe(vfs.dest(config.paths.tmp.css)) .pipe(RevAll.manifest(path.join(config.paths.tmp.css, 'rev-manifest.json'), { base: config.paths.tmp.css, merge: true })) .pipe(vfs.dest(config.paths.tmp.css)) .on('end', () => { cb() }) } /** * html 编译 **/ function compileHtml(cb) { //压缩编译html const htmlMinPipe = lazypipe() .pipe(() => { return usemin({ css: [minifyCSS(), RevAll(), revFormat({ prefix: '.' }), revDel()], html: (config.useHtmlMin.available ? [() => { return htmlmin(config.useHtmlMin.options) }] : []), js: [uglify(), RevAll(), revFormat({ prefix: '.' }), revDel()], assetsDir: './src/' }) }) //合并后的bower文件注入 const injectVendorFiles = lazypipe() .pipe(() => { return inject(vfs.src([`./tmp/static/{js,css}/**/vendor*.{js,css}`], { read: false }), { starttag: '<!-- inject:vendor:{{ext}} -->', ignorePath: '../../../tmp/', relative: true, quiet: true }) }) //公共文件注入 const injectLibFiles = lazypipe() .pipe(() => { return inject(vfs.src([`./tmp/static/{js,css}/**/common*.{js,css}`], { read: false }), { starttag: '<!-- inject:common:{{ext}} -->', ignorePath: '../../../tmp/', relative: true, quiet: true }) }) /** * 将webpack生成的manifest自动注入到html中 */ const injectManifest = lazypipe() .pipe(() => { const manifestFile = glob.sync(path.join(config.paths.tmp.appjs, 'manifest*.js'))[0] const manifestScriptString = `<script>${fs.readFileSync(manifestFile).toString('utf-8')}</script>` return injectString.before('<!-- inject:vendor:js -->', manifestScriptString) }); //处理页面 const injectHtml = (es) => { return es.map((file, cb) => { let cateName = file.path.match(/((.*?)[\/|\\])*([^.]+).*/)[2] vfs.src(file.path) .pipe(rename(cateName + '.html')) .pipe(gulpif( config.merge.vendor.available, injectVendorFiles() )) .pipe(gulpif( config.merge.common.available, injectLibFiles() )) .pipe(gulpif( config.merge.page, inject(vfs.src([`./tmp/static/{js,css}/**/${cateName}.{js,css}`, `./tmp/static/{js,css}/**/${cateName}.*.{js,css}`, `./tmp/static/{js,css}/**/${cateName}/index.{js,css}`], { read: false }), { starttag: '<!-- inject:page:{{ext}} -->', ignorePath: '../../../tmp/', relative: true, quiet: true }) )) .pipe(inject(vfs.src([`./tmp/static/**/{vendor,common}*.{css,js}`, `./tmp/static/fonts/*.{woff2,woff,eot,ttf}`, `./tmp/static/{js,css}/**/${cateName}.{js,css}`, `./tmp/static/{js,css}/**/${cateName}.*.{js,css}`, `./tmp/static/{js,css}/**/${cateName}/index.{js,css}`], { read: false }), { starttag: '<!-- inject:preload:{{ext}} -->', ignorePath: '../../../tmp/', relative: true, quiet: true, transform: function (filepath) { let asVal = '' let crossOrigin = config.useCdn.available const fileExt = path.extname(filepath) switch (fileExt) { case '.js': asVal = 'script'; break; case '.css': asVal = 'style'; break; case '.woff2': case '.woff': case '.eot': case '.ttf': asVal = 'font'; crossOrigin = true; break; default: asVal = ''; } return `<link rel="preload" href="${filepath}" as="${asVal}" ${crossOrigin?'crossorigin="anonymous"':''} />` } })) .pipe(injectManifest()) .pipe(htmlMinPipe()) .pipe(vfs.dest(config.paths.tmp.html)) .on("end", () => { cb() }) }) } /** * 入口页面 */ const indexHtmlFilter = filter('**/index.html', { restore: true }) vfs.src(`${config.paths.src.html}/**/*.html`) .pipe(indexHtmlFilter) .pipe(injectHtml(es)) .pipe(indexHtmlFilter.restore) .pipe(vfs.dest(config.paths.tmp.html)) .on("end", () => { let manifest = vfs.src(`${config.paths.tmp.dir}/**/rev-manifest.json`) vfs.src([`${config.paths.tmp.dir}/**/*`]) .pipe(RevReplace({ manifest: manifest })) .pipe(vfs.dest(config.paths.tmp.dir)) .on('end', () => { del.sync(`${config.paths.tmp.dir}/**/rev-manifest.json`) cb() }) }) } /** * 通过 webpack生成的样式中的静态资源替换为相对地址 */ function webpackCssRelative(cb) { vfs.src(`${config.paths.tmp.css}/*-webpack*.css`) .pipe(cdnify({ base: '', rewriter: (url, process) => { if (/(^(\/)?static)\/(.*?)\.(png|jpe?g|gif|svg|mp4|webm|ogg|mp3|wav|flac|aac|woff2?|eot|ttf|otf)/.test(url)) { url = url.replace(/((\/)?static\/)/, '../') return url } else { return process(url) } } })) .pipe(vfs.dest(config.paths.tmp.css)) .on('end', () => { cb() }) } /** * CDN 地址替换 **/ function cdnReplace(cb) { if (!config.useCdn.available) return cb() vfs.src(`${config.paths.tmp.dir}/**/*.{${config.useCdn.extFile}}`) .pipe(cdnify({ base: config.useCdn.base, rewriter: (url, process) => { if (/http|https|^(\/\/)/.test(url)) { return process(url) } else if (/\.(eot|ttf|woff|woff2)/.test(url)) { url = url.replace(/(\.\.\/fonts)|(\/static\/fonts)/, 'static/fonts') return `${config.useCdn.fonts||config.useCdn.base}${url}` } else if (/\.(png|jpg|gif|svg)/.test(url)) { url = url.replace(/(\.\.\/images)|(\/static\/images)/, 'static/images') return `${config.useCdn.images||config.useCdn.base}${url}` } else if (/\.(js)$/.test(url)) { return `${config.useCdn.js||config.useCdn.base}${url}` } else if (/\.(css)$/.test(url)) { return `${config.useCdn.css||config.useCdn.base}${url}` } else { return process(url) } } })) .pipe(vfs.dest(config.paths.tmp.dir)) .on('end', () => { cb() }) } /** * webp 编译 **/ function compileWebp(cb) { if (!config.useWebp.available) return cb() webp() .then(() => { cb() }) } /** * 完成编译 */ function distComplete(cb) { //清除 tmp 目录 const delTmp = () => del([config.paths.tmp.dir]) vfs.src(`${config.paths.tmp.dir}/**/*`, { base: config.paths.tmp.dir, }) .pipe(vfs.dest(config.paths.dist.dir)) .on('end', () => { spinner.stop() delTmp() console.log(distReport(config.paths.dist.dir)) console.log(chalk.magenta(`The ${config.paths.dist.dir} directory is ready to be deployed.`)) cb() }) } /** * 生产任务 * series 中的任务同步执行 * parallel 中的任务异步执行 */ async.series([ function (next) { delDist(next) }, function (next) { async.parallel([ function (cb) { async.series([ function (next) { copyBowerFiles(next) }, function (next) { async.parallel([ function (cb) { async.series([ function (next) { bowerCustomJs(next) }, function (next) { bowerVendorJs(next) } ], function (error) { if (error) { throw new Error(error) } cb() }) }, function (cb) { bowerCustomCss(cb) } ], function (error) { if (error) { throw new Error(error) } next() }) }, function (next) { bowerVendorCss(next) } ], function (error) { if (error) { throw new Error(error) } cb() }) }, function (cb) { async.series([ function (next) { copyCommonFiles(next) }, function (next) { commonCustomJs(next) }, function (next) { commonJs(next) } ], function (error) { if (error) { throw new Error(error) } cb() }) } ], function (error) { if (error) { throw new Error(error) } next() }) }, function (next) { async.parallel([ function (cb) { reversionBowerCommonJs(cb) }, function (cb) { reversionBowerCommonCss(cb) } ], function (error) { if (error) { throw new Error(error) } next() }) }, function (next) { async.parallel([ function (cb) { handleImages(cb) }, function (cb) { handleFonts(cb) }, function (cb) { handleCustom(cb) } ], function (error) { if (error) { throw new Error(error) } next() }) }, function (next) { async.parallel([ function (cb) { compileCss(cb) }, function (cb) { compileLess(cb) }, function (cb) { compileSass(cb) }, function (cb) { compileStylus(cb) }, function (cb) { handleJs(cb) } ], function (error) { if (error) { throw new Error(error) } next() }) }, function (next) { webpackCssRelative(next) }, function (next) { compileHtml(next) }, function (next) { compileWebp(next) }, function (next) { cdnReplace(next) }, function (next) { distComplete(next) } ], function (err) { if (err) { throw new Error(err) } const buildTime = (new Date() - startTime) / 1000 + 's'; console.log(chalk.green.bold(`Dist completed in ${buildTime}`)) }) }