1 Star 0 Fork 0

caiyue823 / sk2-gulp-cli

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README
MIT

sk2-gulp-cli

项目主页

介绍

一个内置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 ·······项目说明
   

使用说明

###安装

  1. npm install sk2-gulp-cli -Dyarn add sk2-gulp-cli -D
  2. 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模式对代码/资源进行压缩以达到最佳效果。

options

  • production: production模式, 默认: false
  • prod: 等同于production

sk2-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/**',
    }
  }
}

实现

gulpfile相关任务的实现

lint的实现

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的实现

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目录。这样做有以下几点原因
  1. 因为compile生成的都是半成品文件,它们并不具备在生产环境运行的条件,比如生成的js和css文件没有经过压缩,而且没有生成依赖库文件(vendor.css/js), html文件在没有经过useref处理时也没有真正生成对应的依赖库引用。因此它们不能被放入意味着生产的dist包里
  2. 这些半成品可以配合useref任务生成真正的生产环境代码。而在使用useref时, 从temp目录读取半成品文件,然后经过处理写入dist,可以避免读写冲突
  • 这些半成品文件可以配合watcher任务使用,提高在调试时的编译效率。因此需要在子任务的最后添加server.reload
  • compile任务采用并行执行,以提高效率
// 编译,并行执行
const compile = parallel(style, script, page);

build的实现

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时需要考虑以下两点

  1. 开发模式下,监听文件变化,编译调试,需要用到compile任务
  2. 生产模式下,预览production模式打包的项目以获取真实的线上浏览体验,需要用到build任务

serveDev的实现

这是开发环境下的serve,由compile和watcher任务组成

// 开发模式下的serve,编译加watcher即可
const serveDev = series(compile, watcher);

serveProd的实现

这是生产环境下的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的实现

deploy的目标是将打包的dist文件夹git push到github上。这个任务很有意思, 个人思路其实很简单,通过&&连接命令来执行。但中间经过了几轮试错,在这里记录一下过程

round 1. 通过在gulpfile.js里往process.argv里push git相关命令,没有被执行,失败!
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('.');
round 2. 我就在想,是不是push argv的时机晚了,于是在bin/index.js里尝试如上方式。报了&&不是一个gulp task,找不到任务,失败!
round 3. 这时候脑子有点转过弯来了,可能跟push参数根本没有关系,需要寻找一个执行命令行的插件
找到两款,分别叫node-cmd和node-run-cmd(Promise式)
先试了node-cmd,中间经过几轮调试,终于成功push了!!!当时bin/index.js代码长这样
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);
                ...省略
你以为这就完了?NO!在经过短暂的喜悦之后,我陷入了深深的长考。因为这里的实现有着很明显的问题。
  1. 为了保证git命令在gulp后面执行,自己手动执行了gulp
  2. 回调地狱的代码实在是太TMD丑了!!!
  3. bin的代码太不纯洁了!!!
round 4. 既然这样的方式可行,就想着是不是可以把它放在gulpfile.js里,这不是有node-run-cmd支持Promise嘛,顺便优化一下写法
然而node-run-cmd里面存在一些问题,比如只能打印exitcode,又比如它的Promise会在上一个命令出错的情况下继续往then里走,而不是catch
round 5. 切换回node-cmd,用Promise包装了一下
// 将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,
}
回到gulpfile里写git方法,这下就很好看了,并且能在命令行打印每一步该有的输出
// 使用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命令水到渠成,搞定!
// deploy命令,先打包,再git
const deploy = series(build, git);

整体gulpfile实现思路如上,具体代码及详细注释请参考lib/gulpfile.js

将gulpfile包装为cli

实现如下

1. lib下放置config.js, data.js, gulpfile.js, index.js

config.js和data.js分别为gulpfile的相关配置文件,而index.js只是对gulpfile.js 做了一个导出 依次来看

  • config.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.js
// 默认的data参数,可以被项目根目录的project.config.js里的data对象覆盖
module.exports = {
    menus: [...省略],
    //因为被放在lib下,需要往上一级找到package.json
    pkg: require('../package.json'),
    date: new Date(),
}
  • gulpfile.js
// 获取命令行执行目录
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`);
}
  • index.js 可以被省略
module.exports = require('./gulpfile');

2. 在package.json中添加main,指向lib/index.js

3. 在bin下面创建index.js,代码及说明如下

#!/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');

4. 在package.json中添加bin,指向bin/index.js

5. 在package.json中添加files,声明要导出的bin和lib文件夹

{"files": ["bin","lib"]}

6. 最后yarn publish即可

MIT License Copyright (c) 2021 caiyue823 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

简介

gulp cli 展开 收起
JavaScript
MIT
取消

发行版

暂无发行版

贡献者

全部

近期动态

加载更多
不能加载更多了
JavaScript
1
https://gitee.com/caiyue823/sk2-gulp-cli.git
git@gitee.com:caiyue823/sk2-gulp-cli.git
caiyue823
sk2-gulp-cli
sk2-gulp-cli
master

搜索帮助

53164aa7 5694891 3bd8fe86 5694891