一、新建项目
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账号密码,就能上传成功了,有时候输完密码不能成功上传,可能是网络问题,重试几次应该就行了。