封装一个gulp-cli模块

一、新建项目

1.1、新建github仓库

在github上新建一个仓库用来提交模块代码,以及发布到npm上,仓库名与等会的cli模块名保持一致,我这里就都叫 hu-gulp-demo 。

1.2、新建空目录

yarn global add zce-cli

为了图方便,这里我直接使用了拉钩教育汪磊老师的模板创建项目结构,真香。

zce init nm hu-gulp-demo

执行命令创建项目,然后回答一些项目问题就创建成功了。

cd hu-gulp-demo

进入项目目录。

然后初始话git以及绑定远端仓库,先将当前代码上传一次确保没有问题。具体步骤可以参考博客发布项目到NPM。(注意:如果使用了zce-cli创建项目结构,不需要再去创建.gitignore以及设置忽略node_modules文件,因为zce-cli模块都帮我们处理好了,赞!)

二、开发模块

因为我之前写了一个简单的gulp项目,所以直接将其修改一下来封装成cli模块。

2.1、配置依赖

这些依赖在我们之前的项目中都是开发依赖,但是我们需要封装成cli模块的话,就需要将这些依赖设置成"dependencies",这样在其他项目中安装这个cli模块的时候才会安装这些依赖。

"dependencies": {
    "@babel/core": "^7.12.3",
    "@babel/preset-env": "^7.12.1",
    "browser-sync": "^2.26.13",
    "del": "^6.0.0",
    "gulp": "^4.0.2",
    "gulp-babel": "^8.0.0",
    "gulp-clean-css": "^4.3.0",
    "gulp-htmlmin": "^5.0.1",
    "gulp-if": "^3.0.0",
    "gulp-imagemin": "^7.1.0",
    "gulp-load-plugins": "^2.0.5",
    "gulp-sass": "^4.1.0",
    "gulp-swig": "^0.9.1",
    "gulp-uglify": "^3.0.2",
    "gulp-useref": "^5.0.0"
  }

设置好依赖后执行yarn安装这些依赖,这里安装依赖是为了后面测试。

2.2、设置入口文件

我们的入口文件就是lib目录下的index.js,将之前项目的gulpfile.js的内容全部复制过来,但需要进行一些修改。代码有点长哦。

const { src, dest, parallel, series, watch } = require("gulp")


// gulp-load-plugins导出的是一个方法,通过这个方法可以得到一个对象,所有gulp开头的插件都会成为对象上的属性
const loadPlugins = require("gulp-load-plugins")
const plugins = loadPlugins()

// 安装命令:yarn add browser-sync --dev
const browserSync = require("browser-sync")
const bs = browserSync.create() // 创建一个开发服务器

const del = require("del")

const cwd = process.cwd()// 通过cwd获取命令行工作目录,即使用本模块的项目目录,这样我们就能读取到配置文件

// 默认配置
let config = {
  // default config
  build:{
      src: "src",
      dist: "dist",
      public: "public",
      temp: "temp",
      paths: {
          styles: "assets/styles/*.scss",
          scripts: "assets/scripts/*.js",
          pages: "*.html",
          images: "assets/images/**",
          fonts: "assets/fonts/**"
      }
  }
}

try {
    // 如果命令行目录中有pages.config.js配置文件,将其和这里的默认配置进行合并
  const loadConfig = require(`${cwd}/pages.config.js`)
  config = Object.assign({}, config, loadConfig)
} catch (err) {}

/* 清除dist和temp目录 */
const clean = () => {
    return del([config.build.dist, config.build.temp])
}

/* 处理css文件 */
const style = () => {
    return src(config.build.paths.styles, { base: config.build.src, cwd: config.build.src}) 
    // cwd约定从拿个目录去搜索文件,因为config.build.paths.styles是"assets/styles/*.scss",这个路径实在src目录下的所以使用cwd约定搜索目录
            .pipe(plugins.sass({outputStyle: "expanded"}))
            .pipe(dest(config.build.temp))
            .pipe(bs.reload({ stream: true}))// 刷新浏览器
}

// 处理js文件
const script = () => {
    return src(config.build.paths.scripts, { base: config.build.src, cwd: config.build.src })
            .pipe(plugins.babel({ presets:[require("@babel/preset-env")]})) // 如果这里没有配置presets的话,也是可以转换,但是转换前后几乎没什么区别,也可以通过babelrc文件去配置
            .pipe(dest(config.build.temp))
            .pipe(bs.reload({ stream: true}))
}

// 处理html文件
const page = () => {
    return src(config.build.paths.pages, { base: config.build.src, cwd: config.build.src })
            .pipe(plugins.swig({data: config.data, defaults:{cache: false}})) // 配置data,就是模板需要的数据,data: data简写为data;配置defaults:{cache: false},不缓存,不配置可能会修改页面后没有实现更新
            .pipe(dest(config.build.temp))
            .pipe(bs.reload({ stream: true}))
}

// 处理图片
const image = () => {
    return src(config.build.paths.images, { base: config.build.src, cwd: config.build.src })
            .pipe(plugins.imagemin())
            .pipe(dest(config.build.dist))
}

// 字体文件其实只需要拷贝过去就好了,但是里面也有svg文件,所以这里也用imagemin转换一下
const font = () => {
    return src(config.build.paths.fonts, { base: config.build.src, cwd: config.build.src })
            .pipe(plugins.imagemin())
            .pipe(dest(config.build.dist))
}

// 处理一些额外的文件
const extra = () => {
    return src('**', { base: config.build.public, cwd: config.build.src })
            .pipe(dest(config.build.dist))
}


// 启动服务
const serve = () => {
    // 通过watch方法监听文件变化,执行任务
    watch(config.build.paths.styles, { cwd: config.build.src },  style)
    watch(config.build.paths.scripts, { cwd: config.build.src }, script)
    watch(config.build.paths.pages, { cwd: config.build.src }, page)
    watch([
        config.build.paths.images,
        config.build.paths.fonts
    ], { cwd: config.build.src}, bs.reload)
    watch('**', {cwd: config.build.public}, bs.reload)

    bs.init({ // 初始化服务器
        notify: false,// 是否显示 右上角browser-sync是否连接服务的提示
        port:2080,// 端口号
        // open:false,// 是否自动打开浏览器
        // files:"dist/**",// 指定dist下面的所有文件发生变化后更新网站内容(只是监听dist目录下)
        server:{
            // baseDir:"dist", // 网站根目录
            // baseDir:["dist", "src", "public"], // 网站根目录
            baseDir:[config.build.temp, config.build.dist, config.build.public], // 网站根目录
            routes: { // routes里面的配置会优先于baseDir
                "/node_modules": "node_modules" // 对于'/node_modules'开头的文件指到同一个目录下,但似乎没有效果
            }
        }
    })
}

// 将node_modules下要引入的文件打包压缩
const useref = () => {
  return src(config.build.paths.pages, {base: config.build.temp, cwd: config.build.temp})
    .pipe(plugins.useref({ searchPath: ['.', '../.'] })) // 由于上面cwd约定了在config.build.temp这个地址,所以这里.就代表temp目录,../.就表示根目录
    // 使用useref会创建一个文件转换流,这个转换流会去把我们代码中的那些构建注释做一个转换
    // 需要配置searchPath参数,告诉useref去哪个文件下找到这个要被操作的文件
    // 例如main.css文件要去dist目录下找,/node_modules之类的文件就要去项目根目录下找
    // 像这种数组形式的参数我们一般将使用多的放在前面,这样在前面找到了就不会往后执行,性能会有一些优化
    .pipe(plugins.if(/\.js$/,plugins.uglify())) 
    .pipe(plugins.if(/\.css$/,plugins.cleanCss()))
    .pipe(plugins.if(/\.html$/,plugins.htmlmin({
      collapseWhitespace:true, // 清除换行符
      minifyCSS: true, // 压缩css
      minifyJS: true // 压缩js
    })))
    .pipe(dest(config.build.dist))
}

// 组合上面的任务
// 因为这些任务并没有什么牵连,可以同时进行,所以使用parallel
// 定义compile处理src目录下的文件
const compile = parallel(style, script, page)

// 定义build处理所有文件
// 因为我们要先删除,再生成,所以要用series串行执行任务
// 上线之前执行的任务
const build = series(
  clean, 
  parallel(
    series(compile, useref),
    extra, 
    image, 
    font,
    )
)

const develop = series(compile, serve)
// yarn gulp build --gulpfile ./node_modules/hu-gulp-demo/lib/index.js --cwd .
// 指定gulpfile文件路径以及工作目录
module.exports = {
    clean,
    build,
    develop
}

2.3、cli执行入口

在项目根目录下创建bin目录,bin目录下新建 hu-gulp-demo.js文件,为了方便一样与项目同名,这就是cli执行入口。

创建好后别忘了去package.json中指定一下,添加:

"bin": "bin/hu-gulp-demo.js"

以及在 "files"属性中加上bin

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

输入测试代码。

console.log("this is hu-gulp-demo")

然后执行:yarn link链接全局。
我们就能直接执行命令:hu-gulp-demo,就会打印出: “this is hu-gulp-demo”。
如果不能使用yarn link链接全局后的cli模块,可以参考这篇博客

测试过后我们来编写正式代码,内容如下:

#!/usr/bin/env node

// 我们可以通过process.argv获取到命令行传入的数据,拿到的是一个数组
// console.log(process.argv);
// argv中前两个数据是固定的,第一个是node.exe,第二个是当前文件(bin/hu-gulp-demo.js)的路径
// 后面的就是我们传入的数据,以空格分割为数组成员

// 因为空格分开的每一个内容都是argv数组当中的独立成员,所以分开push
process.argv.push("--cwd")
process.argv.push(process.cwd()) // 当前命令行所在的目录
process.argv.push("--gulpfile")
process.argv.push(require.resolve("..")) // 会去找到上级目录,然后根据package.json文件找到lib/index.js,里面的内容正式gulpfile.js内容

require("gulp/bin/gulp")

为什么这样写呢,我们可以在node_modules/.bin中找到gulp.cmd文件,其内容为:

@IF EXIST "%~dp0\node.exe" ( 
// IF判断,EXIST是否存在,%~dp0当前目录,这句话的意思就是判断当前目录是否存在node.exe
// 存在就走这里面的代码,不存在就走下面的。因为我们node都是安装在全局的,所以执行的是下面的代码
  "%~dp0\node.exe"  "%~dp0\..\gulp\bin\gulp.js" %*
) ELSE (
// 上面这两韩就是配置了一下环境变量,这里我们不用管
  @SETLOCAL
  @SET PATHEXT=%PATHEXT:;.JS;=;%
  node  "%~dp0\..\gulp\bin\gulp.js" %*
  // 主要看这行,node就是node去执行,当前目录也就是.bin
  // %~dp0\..\gulp\bin\gulp.js也就是.bin\..\gulp\bin\gulp.js
)

我们去找到.bin…\gulp\bin\gulp.js

#!/usr/bin/env node

require('gulp-cli')();

我们也不用继续往下看了,可以直接引用这个文件,也就是require("gulp/bin/gulp")

好了,到这里我们就算写好cli入口文件了,只要执行hu-gulp-demo命令,就会去执行gulp。

创建这个cli文件的主要目的,是为了让其他项目在使用本模块的时候,可以省去安装gulp依赖,直接通过 hu-gulp-demo 就能对项目进行构建。

三、测试模块

我们已经通过yarn link将模块链接全局了,只要在一个项目中执行:hu-gulp-demo build,就会对项目进行构建。
注意:如果项目中的内容不是在src目录下,需要创建一个pages.config.js配置文件,内容就和上面2.2中代码里的config一样。

由于gulp-imagemin中有些依赖在国外站点,可能会提示optipng和gifsicle找不到,可以在项目中使用cnpm重新安装gulp-imagemin,注意是项目中,不是cli模块里。

四、发布模块

yarn publish --registry=https://registry.yarnpkg.com

输入版本号、npm账号密码,就能上传成功了,有时候输完密码不能成功上传,可能是网络问题,重试几次应该就行了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值