一个内置gulp-cli & gulpfile & 灵活配置的工作流cli 模板文件脚手架项目见sk2-gulp-pages
└── sk2-gulp-cli ······项目根目录
├─ bin ·············node命令目录
│ ├─ index.js ·····bin入口文件
├─ lib ·············gulp相关文件
│ ├─ cmdPromise.js ····将node-cmd.run函数包装成Promise函数exec
│ ├─ config.js ····gulefile相关配置,主要为路径默认配置
│ ├─ data.js ······页面相关默认配置
│ ├─ gulpfile.js ··主gulpfile文件
│ ├─ index.js ·····导出gulefile,main入口文件
├─ .eslintrc.js ····eslint默认配置,可以被项目的eslint配置覆盖
├─ .gitignore ······git忽略文件配置
├─ .npmrc ··········npm镜像下载地址
├─ LICENSE ········证书
├─ lint.yml ········暂不使用
├─ package.json ····npm包说明文件
├─ project.config.js ····项目默认配置样本,为lib中config和data的合集。
本项目中无用,需在使用项目中添加
├─ README.md ·······项目说明
###安装
npm install sk2-gulp-cli -D
或 yarn add sk2-gulp-cli -D
sk2-gulp-cli taskName
, 可用命令如下###命令
sk2-gulp-cli lint
scss/js文件lint检查
sk2-gulp-cli compile
scss/js/html文件编译。编译后的css/js/html会被放入temp
文件夹中。
sk2-gulp-cli serve
使用内置服务器预览、监听、调试代码。编译后的css/js/html会被放入temp
文件夹中。
open
: 是否在启动服务器时打开浏览器窗口, 默认: false
port
: 设置端口号, 默认: 2080
sk2-gulp-cli build
打包项目并将文件放入dist
目录. 推荐使用production模式对代码/资源进行压缩以达到最佳效果。
production
: production模式, 默认: false
prod
: 等同于productionsk2-gulp-cli start
使用内置服务器预览production模式打包的项目以获取真实的线上浏览体验。
这个命令其实是sk2-gulp-cli serve --prod
的别名。
注意:在这个模式下,源文件监听/调试将被关闭。如果你想要调试,请使用sk2-gulp-cli serve
open
: 是否在启动服务器时打开浏览器窗口, 默认: false
port
: 设置端口号, 默认: 2080
sk2-gulp-cli deploy
将打包的dist
文件夹push到git仓库
branch
: 要push的分支名, 默认: 'gh-pages'
sk2-gulp-cli clean
清空dist
& temp
文件
你可以通过在自己的项目根目录新建一个project.config.js,根据自己的需要来覆盖这些配置。 默认配置如下。项目根目录提供了一份project.config.js,可以直接复制它到你的项目根目录进行修改即可。
module.exports = {
data: {
menus: [
{
name: 'Home',
icon: 'aperture',
link: 'index.html'
},
{
name: 'Features',
link: 'features.html'
},
{
name: 'About',
link: 'about.html'
},
{
name: 'Contact',
link: '#',
children: [
{
name: 'Twitter',
link: 'https://twitter.com/w_zce'
},
{
name: 'About',
link: 'https://weibo.com/zceme'
},
{
name: 'divider'
},
{
name: 'About',
link: 'https://github.com/zce'
}
]
}
],
pkg: require('./package.json'),
date: new Date(),
},
config: {
SRC: 'src',
DIST: 'dist',
TEMP: '.tmp',
PUBLIC: 'public',
PATHS: {
style: 'assets/styles/*.scss',
script: 'assets/scripts/*.js',
page: '*.html',
image: 'assets/images/**',
font: 'assets/fonts/**',
}
}
}
lint任务分为styleLint和scriptLint两个子任务,任务的实现相对简单, 分别定义两个任务,引入对应的lint插件,调用相应api即可。以下是scriptLint的实现
const scriptLint = () => {
return src(PATHS.script, srcConfig)
//执行lint
.pipe(plugins.eslint())
//输出lint结果至控制台
.pipe(plugins.eslint.format())
// 如果报错终止执行
.pipe(plugins.eslint.failAfterError())
}
在使用eslint插件时需注意的是,当手动传入参数至gulp-eslint插件时,插件的参数格式与.eslintrc.js不匹配。 例如,gulp-eslint插件的envs,globals属性均为数组形式,而.eslintrc.js对应的属性为对象。 因此更好的方式应该是不处理参数,让插件在运行时自动读取运行时项目根目录的.eslintrc.js
const lint = parallel(styleLint, scriptLint);
在实现lint命令时使用parallel并行执行,一是缩短时间,二是因为任何一项lint任务一旦报错都无需往下继续。 当然这样做也有一个缺点,子任务的报错日志会交替输出,阅读上有点麻烦。
compile任务分为css/js/html三个子任务,输出半成品的css/js/html文件,配合useref/watcher任务使用。 以js任务为例,实现如下
// script编译
const script = () => {
return src(PATHS.script, srcConfig)
.pipe(plugins.babel({
// 使用require引用,当变成cli时,require找到的是当前cli下的node_modules里的包
presets: [require('@babel/preset-env')]
}))
.pipe(dest(TEMP))
// 与watcher中的watch任务配合,实现浏览器刷新
.pipe(server.reload({stream: true}))
}
在实现compile子任务时需要注意以下几点
temp
而不是dist
目录。这样做有以下几点原因半成品
文件,它们并不具备在生产环境运行的条件,比如生成的js和css文件没有经过压缩,而且没有生成依赖库文件(vendor.css/js),
html文件在没有经过useref处理时也没有真正生成对应的依赖库引用。因此它们不能被放入意味着生产的dist
包里半成品
可以配合useref任务生成真正的生产环境代码。而在使用useref时,
从temp
目录读取半成品文件,然后经过处理写入dist
,可以避免读写冲突server.reload
// 编译,并行执行
const compile = parallel(style, script, page);
build任务的实现如下
// 打包命令,需要先clean,然后并行执行(css/js/html的编译与引用查找,以及其他静态资源的压缩)
const build = series(clean, parallel(series(compile, useref), image, font, extra));
由于build需要区分开发/生产模式,因此在useref/image/font等子任务中均需要做相应处理(不包括compile)。 以下代码通过插件从命令行中获取isProd。当isProd为true时, 需要对所有的资源进行压缩/混淆/查找引用等处理
// 转化命令行参数为一个对象
const args = require('node-args-parser')(process.argv);
console.log('args', args);
// 处理相关命令行参数
// 提取prod参数供后续使用
const isProd = args['-production'] || args['-prod'] || false;
其中useref的实现如下
const useref = () => {
return src(PATHS.page, tempConfig)
// tempConfig中的cwd:TEMP会将useref的工作目录改为TEMP,
// 加上..才是项目根目录,从而找到node_modules
.pipe(plugins.useref({searchPath: [TEMP, '.', '..']}))
// 仅在prod模式下进行相应文件压缩
.pipe(plugins.if(isProd && /\.css$/, plugins.cleanCss()))
.pipe(plugins.if(isProd && /\.js$/, plugins.uglify()))
.pipe(plugins.if(isProd && /\.html$/, plugins.htmlmin({
collapseWhitespace: true,
removeComments: true,
minifyJS: true,
minifyCSS: true,
})))
.pipe(dest(DIST));
}
image任务
const image = () => {
return src(PATHS.image, srcConfig)
// 仅在prod模式下进行压缩
.pipe(plugins.if(isProd, plugins.imagemin()))
.pipe(dest(DIST));
}
实现serve时需要考虑以下两点
这是开发环境下的serve,由compile和watcher任务组成
// 开发模式下的serve,编译加watcher即可
const serveDev = series(compile, watcher);
这是生产环境下的serve,由compile和watcher任务组成
// prod模式下的serve,需要先打包,然后watcher即可。
// 这时候watcher中不监听任何文件变化,相关逻辑在watcher中处理
const serveProd = series(build, watcher);
在watcher任务中根据isProd处理相关逻辑,代码如下
const watcher = () => {
// 开发模式下监听文件变化;prod模式不监听
if (!isProd) {
// 监听相关css/js/html文件,并重新执行对应的编译
watch(PATHS.style, srcConfig, style);
watch(PATHS.script, srcConfig, script);
watch(PATHS.page, srcConfig, page);
// 监听图片/字体/其他静态资源文件,刷新浏览器
watch([PATHS.image, PATHS.font], srcConfig, server.reload);
watch('**', publicConfig, server.reload);
}
const serverCfg = {
// prod模式只使用dist目录
// 开发模式下,需使用temp里的css/js/html文件,src下的图片/字体文件,public下的其他静态资源文件
baseDir: isProd ? [DIST] : [TEMP, SRC, PUBLIC],
// prod模式下,不需要任何路由;开发模式下,需要通过路由找到/node_modules下的vendor文件
routes: !isProd && {
'/node_modules': 'node_modules',
}
}
server.init({
// 优先使用命令行参数的设置
port: args['-port'] || 2080,
open: args['-open'] || false,
server: {
...serverCfg,
},
})
}
而最后向外暴露出的serve命令如下
// 对外暴露的serve命令,根据命令行prod参数执行不同的serve
const serve = isProd ? serveProd : serveDev;
deploy的目标是将打包的dist文件夹git push到github上。这个任务很有意思, 个人思路其实很简单,通过&&连接命令来执行。但中间经过了几轮试错,在这里记录一下过程
function gitPush(branchName = 'gh-pages') {
//git add foldername
//git commit -m "commit operation"
process.argv.push('deploy');
process.argv.push('&&');
process.argv.push('git');
process.argv.push('add');
process.argv.push('.');
const args = require('node-args-parser')(process.argv);
const gulpfilePath = require.resolve('..');
const cmd = require('node-cmd');
const cwd = process.cwd();
process.argv.push('--gulpfile');
process.argv.push(gulpfilePath);
process.argv.push('--cwd');
process.argv.push(cwd);
handleDeploy();
function handleDeploy() {
// 如果非deploy命令,走require方式执行
if (process.argv[2] !== 'deploy') {
require('gulp/bin/gulp');
return;
}
const branchName = args['-branch'] || 'gh-pages';
console.log(gulpfilePath, cwd);
// 如果是deploy,为了保证命令的先后执行,需要在这里手动执行gulp
cmd.run(`gulp deploy --gulpfile ${gulpfilePath} --cwd ${cwd}`, (e, data) => {
if (e) {
console.log(e);
return;
}
console.log(data);
cmd.run('git add .', (e) => {
if (e) {
console.log(e);
...省略
// 将cmd run方法包装成一个Promise方法exec
const cmd = require('node-cmd');
function exec(line) {
return new Promise(function (resolve, reject) {
cmd.run(line, (err, data) => {
if (err) {
reject(err);
return;
}
resolve(data);
})
})
}
module.exports = {
exec,
}
// 使用node-cmd插件异步执行git cmd
const git = (done) => {
return exec(`git add ./${DIST}`)
.then((data) => {
console.log('git add success', data);
return exec(`git commit -m "commit ${DIST} ${new Date().toLocaleString()}"`)
})
.then((data) => {
console.log('git commit success', data);
return exec(`git push origin ${args['-branch'] || 'gh-pages'}`)
})
.then((data) => {
console.log('git push success', data);
})
.catch(e => {
console.log(e);
done(false);
})
}
// deploy命令,先打包,再git
const deploy = series(build, git);
整体gulpfile实现思路如上,具体代码及详细注释请参考lib/gulpfile.js
实现如下
config.js和data.js分别为gulpfile的相关配置文件,而index.js只是对gulpfile.js 做了一个导出 依次来看
// 默认的config参数,可以被项目根目录的project.config.js里的config对象覆盖
module.exports = {
SRC: 'src',
DIST: 'dist',
TEMP: 'temp',
PUBLIC: 'public',
PATHS: {
style: 'assets/styles/*.scss',
script: 'assets/scripts/*.js',
page: '*.html',
image: 'assets/images/**',
font: 'assets/fonts/**',
}
}
// 默认的data参数,可以被项目根目录的project.config.js里的data对象覆盖
module.exports = {
menus: [...省略],
//因为被放在lib下,需要往上一级找到package.json
pkg: require('../package.json'),
date: new Date(),
}
// 获取命令行执行目录
const CWD = process.cwd();
// 设置配置文件名称
const configFileName = 'project.config.js';
// 读取gulp 编译相关默认配置
let cfg = {
data: require('./data'),
config: require('./config')
};
try {
const projectCfg = require(resolve(CWD, configFileName));
cfg = Object.assign({}, cfg, projectCfg);
} catch (e) {
console.log(`read ${configFileName} error`);
}
module.exports = require('./gulpfile');
#!/usr/bin/env node
/**
* 通过往命令行参数里添加--gulpfile,将运行项目的gulpfile指向当前cli包的lib/gulpfile
* 通过往命令行参数里添加--cwd,并将运行项目的cwd传入,以防止gulp将运行目录重置为当前cli包目录
*/
process.argv.push('--gulpfile');
// require.resolve仅解析当前文件路径,并不执行当前文件代码,通过入参..的方式找到上一级目录(cli包目录)。
// 此时根据node查找模块的机制,会找到包目录的package.json里的main字段,从而定位到lib下的index文件
process.argv.push(require.resolve('..'));
process.argv.push('--cwd');
process.argv.push(process.cwd());
// console.log(process.argv);
// 这里使用了npm查找包的机制,会找到cli包下的node_modules里的gulp
// 而gulp中执行了require('gulp-cli')(),真正去执行gulpfile.js
require('gulp/bin/gulp');
{"files": ["bin","lib"]}
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。