Appearance
如何去构建提取多个项目中自动化过程?
目标
如果我们有多个差不多的项目,那么每个项目的任务基本都是一样的,那我们就要重复去构建这么多的任务。我们就需要复用gulpfile.js
,如果我们使用代码片段去构建,那么如果需要整体修改某个部分,是不利于整体维护的。说了这么多目的就是 —— 我们要提取一个可复用的自动化构建工作流。
只要我们安装一个npm
包csf-build
(这里可以起别的名字),就可以自动把gulpfile.js
里面执行的任务包进来,我们使用命令可以一键构建生产项目。
原理
如同上述的逻辑,我们就需要把我们之前封装好的gulpfile.js
进行提取。通过创建一个新的npm
模块,包装一下gulp
,然后把自动化构建流包装进去。
Gulpfile + Gulp CLI = gulp-pages
创建
创建仓库并初始化项目文件
- 在GitHub上创建一个仓库,命名为
csf-gulp-build
- 在本地创建
csf-gulp-build
目录,并且git init
初始化 - 将模板目录安装上gulp-build-demo-temp,主要把里面的文件copy出来,不过一般都是使用脚手架安装目录,这里只是一个demo
- 创建远程仓库
git remote add origin git@github.com:a1burning/csf-gulp-build.git
,然后将代码git add .
->git commit -m 'demo init'
->git push origin master
这样准备工作就做好了
lib
里面的index.js
是一个入口文件。
提取
- 将gulpfile.js文件中的内容拷贝到index.js中
- 将gulpfile里面需要的依赖进行copy(在package.json中作为dependencies依赖)
js
"dependencies": {
"@babel/core": "^7.11.6",
"@babel/preset-env": "^7.11.5",
"browser-sync": "^2.26.12",
"del": "^5.1.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-less": "^4.0.1",
"gulp-load-plugins": "^2.0.4",
"gulp-rename": "^2.0.0",
"gulp-sass": "^4.1.0",
"gulp-swig": "^0.9.1",
"gulp-uglify": "^3.0.2",
"gulp-useref": "^4.0.1",
"less-plugin-autoprefix": "^2.0.0"
}
- 使用
npm install
安装这些依赖
修改
下面的步骤在原来的基础上修改,如果是新项目请跳转下一个章节
- 在原项目中把
gulpfile.js
中的代码删掉 package.json
中的devDependencies
删掉- 把
node_modules
的模块删掉
本地测试
- 在csf-gulp-build模块中,使用
npm link
链接到全局。 - 再在Gulpauto中使用
npm link csf-gulp-build
链接到项目中,这个时候可以看到项目中多了一个node_modules
文件夹。里面会有文件夹csf-gulp-build
,并且可以看到是一个软连接
- 在Gulpauto项目的gulpfile.js文件中写
js
// 直接导入node_modules里面的模块
module.exports = require('csf-gulp-build')
- 在Gulpauto项目中安装一下别的依赖
npm install
- 这个时候在本地命令行
npm run build
,会看到gulp没有安装的错误,现在先手动安装npm install gulp-cli --save-dev
和npm install gulp --save-dev
(我们现在在开发阶段需要手动安装,但是在发布之后就不需要手动安装) - 再尝试启动
npm run build
会报错引用的./package.json
不对,那么把特殊项目中用到的数据,创建一个pages.config.js
的文件,输出出去
js
module.exports = {
data: {
array: [],
pkg: require('./package.json'),
date: new Date()
}
}
- 原来的地方这么引用
js
// cwd方法返回当前命令行所在的工作目录
const cwd = process.cwd()
// 读取pages.config.js文件,防止出错
let config = {
// default config
}
try {
// require当前命令行下的package.json文件
const loadConfig = require(`${cwd}/pages.config.js`)
// 如果有传入就和默认的合并
config = Object.assign({}, config, loadConfig)
} catch (e) {// 如果没有就执行default config
}
const page = () => {
return src('src/**/*.html', { base: 'src' })
// data里面传的值是config.data
.pipe(plugins.swig({data: config.data, defaults: { cache: false }}))
.pipe(dest('temp'))
.pipe(bs.reload({ stream: true }))
}
- 之后运行报错
cannot find module '@babel/preset-env'
,我们在csf-gulp-build的index.js下面修改script任务
js
const script = () => {
return src('src/assets/scripts/*.js', { base: 'src' })
.pipe(plugins.babel({
// 使用require的方式先去本身的node_modules中寻找,没有往上级的node_modules中寻找
presets: [require('@babel/preset-env')]
}))
.pipe(dest('temp'))
.pipe(bs.reload({ stream: true }))
}
- 之后会报imagemin的错误,去
csf-gulp-build
文件中命令行用cnpm i gulp-imagemin --save-dev
重新安装一下gulp-imagemin,之后去Gulpauto
中npm run build
可以启动成功
bash
npm run build
# > gulpauto@1.0.0 build E:\professer\Gulpauto
# > gulp build
# Using gulpfile E:\professer\Gulpauto\gulpfile.js
# Starting 'build'...
# gulp-imagemin: Minified 1 image (saved 679 B - 5.9%)
# gulp-imagemin: Minified 2 images (saved 22.8 kB - 26.5%)
# Finished 'build' after 11 s
# 这里只有暴露的build任务,其他任务都隐藏,如果想要这些任务名称,需要在gulpfile.js中结构任务名称再导出。
# const { clean, develop } = require('csf-gulp-build')
- 之后检查一下输出的dist文件,看到html/js/css都被压缩,然后模板语法都有替换,这部分已经没有问题了。
路径配置
在原有的基础上,对输入路径和输出路径提供暴露配置接口,这样更加的灵活。
把路径都提取到默认的config中:
js
// cwd方法返回当前命令行所在的工作目录
const cwd = process.cwd()
// 读取pages.config.js文件,防止出错
let config = {
// default config
build: {
src: 'src',
dist: 'dist',
temp: 'temp',
public: 'public',
paths: {
styles: 'assets/styles/*.scss',
lessStyles: 'assets/styles/*.less',
scripts: 'assets/scripts/*.js',
pages: '**/*.html',
images: 'assets/images/**',
fonts: 'assets/fonts/**'
}
}
}
try {
// require当前命令行下的package.json文件
const loadConfig = require(`${cwd}/pages.config.js`)
// 如果有传入就和默认的合并
config = Object.assign({}, config, loadConfig)
} catch (e) {
// 如果没有就执行default config
}
// 创建清除文件任务
const clean = () => {
return del([config.build.dist, config.build.temp])
}
// 创建sass任务
const style = () => {
// 可以使用字符串拼接的方式
return src(config.build.src+'/'+config.build.paths.styles, { base: config.build.src})
.pipe(plugins.sass({ outputStyle: 'expanded' }))
.pipe(dest(config.build.temp))
.pipe(bs.reload({ stream: true }))
}
// 创建less任务
const lessStyle = () => {
// cwd是指从哪个目录下开始寻找,和path的路径对应即可
return src(config.build.paths.lessStyles, { base: config.build.src, cwd: config.build.src})
.pipe(plugins.less({
plugins: [autoprefix]
}))
.pipe(dest(config.build.temp))
.pipe(bs.reload({ stream: true }))
}
// 创建babel任务
const script = () => {
return src(config.build.paths.scripts, { base: config.build.src, cwd: config.build.src })
.pipe(plugins.babel({
presets: [require('@babel/preset-env')]
}))
.pipe(dest(config.build.temp))
.pipe(bs.reload({ stream: true }))
}
// 创建模板引擎任务
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 }}))
.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))
}
// 图片压缩任务
const font = () => {
return src(config.build.paths.fonts, { base: config.build.src, cwd: config.build.src })
.pipe(plugins.imagemin())
.pipe(dest(config.build.dist))
}
// 将public的任务进行额外输出
const extra = () => {
return src('**', { base: config.build.public,cwd: config.build.public })
.pipe(dest(config.build.dist))
}
// 创建服务任务
const serve = () => {
// 这边也可以指定第二个参数cwd
watch(config.build.paths.styles, { cwd: config.build.src }, style)
watch(config.build.paths.lessStyles, { cwd: config.build.src }, lessStyle)
watch(config.build.paths.scripts, { cwd: config.build.src }, script)
watch(config.build.paths.pages, page)
watch([
config.build.paths.images,
config.build.paths.fonts,
], { cwd: config.build.src }, bs.reload)
// 因为public目录不同,所以单独抽离出来
watch(['**'], { cwd: config.build.src }, bs.reload)
// 进行初始化
bs.init({
notify: false,
port: 2080,
server: {
baseDir: [config.build.temp, config.build.src, config.build.public],
routes: {
'/node_modules': 'node_modules'
}
}
})
}
const useref = () => {
return src(config.build.paths.pages, { base: config.build.temp, cwd: config.build.temp })
// .指的是项目根目录,不需要修改
.pipe(plugins.useref({ searchPath: [config.build.temp, '.']}))
.pipe(plugins.if(/\.js$/, plugins.uglify()))
.pipe(plugins.if(/\.css$/, plugins.cleanCss()))
.pipe(plugins.if(/\.html$/, plugins.htmlmin({
collapseWhitespace: true,
minifyCSS: true,
minifyJS: true
})))
.pipe(dest(config.build.dist))
}
const compile = parallel(style, lessStyle, script, page)
const build = series(
clean,
parallel(
series(compile, useref),
image,
font,
extra
)
)
const develop = series(compile, serve)
module.exports = {
clean,
build,
develop
}
运行npm run build
和npm run serve
通过,那么我们在项目page.config.js
中,修改输入输出目录检查一下。
js
module.exports = {
build: {
src: 'src',
dist: 'release',
temp: '.tmp',
public: 'public',
paths: {
styles: 'assets/styles/*.scss',
lessStyles: 'assets/styles/*.less',
scripts: 'assets/scripts/*.js',
pages: '**/*.html',
images: 'assets/images/**',
fonts: 'assets/fonts/**'
}
},
data: {
menus: [],
pkg: require('./package.json'),
date: new Date()
}
}
运行之后看到输出目录变成了release
,临时目录编程了.tmp
封装gulp-cli
一、删除gulpfile.js
- 删除
gulpfile.js
之后,运行npm run build
会报错No gulpfile found
。 - 这个时候运行
gulp --gulpfile ./node_modules/csf-gulp-build/lib/index.js
是可以找到的,但是报错Task never defined: default
,没有默认任务 - 运行build任务
gulp build --gulpfile ./node_modules/csf-gulp-build/lib/index.js
可以看到能够正常运行,但是此时工作目录进行了变更Working directory changed to E:\professer\lagou\Gulpauto\node_modules\csf-gulp-build\lib
,项目的根目录就有了问题。需要再指定一下工作目录为当前目录gulp build --gulpfile ./node_modules/csf-gulp-build/lib/index.js --cwd .
二、创建cli
上面路径太过复杂,那么我们封装一个CLI
1.在csf-gulp-build
下面创建一个bin/csf-gulp-build.js
文件,这个文件是cli的执行入口,必须出现在package.json中
js
// package.json
"bin": "bin/csf-gulp-build.js"
// bin/csf-gulp-build.js
#!/usr/bin/env node
console.log('csf-gulp-build')
- 重新将
csf-gulp-buiild
项目npm link
(记得link之前先unlink),运行
bash
csf-gulp-build
# 可以看到正常输出
# csf-gulp-build
- 继续编辑
bin/csf-gulp-build.js
文件
js
#!/usr/bin/env node
// 获取一下命令行参数,argv是一个数组
// 数组第一个参数是node.exe固定的,第二个参数是当前文件路径也是固定的
// 之后用户的参数都跟在后面
console.log(process.argv)
// 往命令行参数中push,
process.argv.push('--cwd')
// 当前命令行所在目录
process.argv.push(process.cwd())
// 还要push gulpfile的路径
process.argv.push('--gulpfile')
// 找的是lib目录下的index.js
// require是载入这个模块,resolve是找到这个模块对应的路径,参数是相对路径
// 相对目录就是../lib/index
// 这里直接写..就可以,因为..找的是csf-gulp-build根目录,里面会自动找package.json里面的main属性下的lib/index.js文件
process.argv.push(require.resolve('..'))
// 直接载入gulp.js去执行gulp-cli
require('gulp/bin/gulp')
- 去
Gulpauto
中运行csf-gulp-build build
可以看到正常运行,目录也是正确。这样就不要求本地目录必须添加gulpfile.js了,就可以把gulp
全部都包装在csf-gulp-build
中了。
发布
- 在package.json中添加,在publish的时候,可以将两个目录都发布
js
"files": [
"lib",
"bin"
]
npm publish
发布成功 csf-gulp-buildyarn publish --registry https://registry.yarnpkg.com
yarn发布到yarn的镜像源,和npm是同步的。
使用
- 创建一个新的文件夹
csf-gulp-demo
,将public/
、src/
、pages.config.js
复制进去 - 用
npm init -y
创建一个package.json文件。 npm i csf-gulp-build --save-dev
csf-gulp-build build
即可进行build发布- 还可以在
package.json
文件中添加script
bash
"scripts": {
"clean": "csf-gulp-build clean",
"serve": "csf-gulp-build develop"
}
直接npm run clean
,npm run serve
,但是npm run build
不能使用
在安装的时候,因为我们publish是在npm源,我们下载的时候是淘宝源,可能会有时间差。可以去淘宝源上检查有没有这个文件
在安装的时候建议使用cnpm install csf-gulp-build,这样gulp-imagemin不会出错。
总结
- 给要做的npm包创建一个仓库,之后要在仓库中提交。
- 提取gulpfile.js,安装对应依赖,修改里面的配置和出错的地方
- 把原来的gulpfile.js进行删除,使用npm link进行全局连接,修改问题,实现命令行执行
- 把npm包封装gulp-cli,进行发布
- 找一个别的项目,下载npm包,直接使用