cyb-cli
Version:
塞伯坦CYB-CLI是面向前端模块化工程的构建工具。主要目的是帮助开发者统一前端开发模式和项目开发结构,提高功能扩展和降低维护成本,自动化前端工作流,提高开发效率和开发质量。
1,066 lines (988 loc) • 29.3 kB
JavaScript
/**
* =================================
* @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}`))
})
}