Skip to content

如何去构建提取多个项目中自动化过程?

目标

如果我们有多个差不多的项目,那么每个项目的任务基本都是一样的,那我们就要重复去构建这么多的任务。我们就需要复用gulpfile.js,如果我们使用代码片段去构建,那么如果需要整体修改某个部分,是不利于整体维护的。说了这么多目的就是 —— 我们要提取一个可复用的自动化构建工作流

只要我们安装一个npmcsf-build(这里可以起别的名字),就可以自动把gulpfile.js里面执行的任务包进来,我们使用命令可以一键构建生产项目。

image

原理

如同上述的逻辑,我们就需要把我们之前封装好的gulpfile.js进行提取。通过创建一个新的npm模块,包装一下gulp,然后把自动化构建流包装进去。

Gulpfile + Gulp CLI = gulp-pages

创建

创建仓库并初始化项目文件

  1. 在GitHub上创建一个仓库,命名为csf-gulp-build
  2. 在本地创建csf-gulp-build目录,并且git init初始化
  3. 将模板目录安装上gulp-build-demo-temp,主要把里面的文件copy出来,不过一般都是使用脚手架安装目录,这里只是一个demo
  4. 创建远程仓库git remote add origin git@github.com:a1burning/csf-gulp-build.git ,然后将代码git add .-> git commit -m 'demo init' -> git push origin master

image

这样准备工作就做好了

lib里面的index.js是一个入口文件。

提取

  1. gulpfile.js文件中的内容拷贝到index.js中
  2. 将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"
  }
  1. 使用npm install安装这些依赖

修改

下面的步骤在原来的基础上修改,如果是新项目请跳转下一个章节

  1. 在原项目中把gulpfile.js中的代码删掉
  2. package.json中的devDependencies删掉
  3. node_modules的模块删掉

本地测试

  1. 在csf-gulp-build模块中,使用npm link链接到全局。
  2. 再在Gulpauto中使用npm link csf-gulp-build链接到项目中,这个时候可以看到项目中多了一个node_modules文件夹。里面会有文件夹csf-gulp-build,并且可以看到是一个软连接

image

  1. 在Gulpauto项目的gulpfile.js文件中写
js
// 直接导入node_modules里面的模块
module.exports = require('csf-gulp-build')
  1. 在Gulpauto项目中安装一下别的依赖npm install
  2. 这个时候在本地命令行npm run build,会看到gulp没有安装的错误,现在先手动安装npm install gulp-cli --save-devnpm install gulp --save-dev(我们现在在开发阶段需要手动安装,但是在发布之后就不需要手动安装)
  3. 再尝试启动npm run build会报错引用的./package.json不对,那么把特殊项目中用到的数据,创建一个pages.config.js的文件,输出出去
js
module.exports = {
  data: {
    array: [],
    pkg: require('./package.json'),
    date: new Date()
  }
}
  1. 原来的地方这么引用
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 }))
}
  1. 之后运行报错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 }))
}
  1. 之后会报imagemin的错误,去csf-gulp-build文件中命令行用cnpm i gulp-imagemin --save-dev重新安装一下gulp-imagemin,之后去Gulpautonpm 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')
  1. 之后检查一下输出的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 buildnpm 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

  1. 删除gulpfile.js之后,运行npm run build会报错No gulpfile found
  2. 这个时候运行gulp --gulpfile ./node_modules/csf-gulp-build/lib/index.js是可以找到的,但是报错Task never defined: default,没有默认任务
  3. 运行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')
  1. 重新将csf-gulp-buiild项目npm link(记得link之前先unlink),运行
bash
csf-gulp-build
# 可以看到正常输出
# csf-gulp-build
  1. 继续编辑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')
  1. Gulpauto中运行csf-gulp-build build可以看到正常运行,目录也是正确。这样就不要求本地目录必须添加gulpfile.js了,就可以把gulp全部都包装在csf-gulp-build中了。

发布

  1. 在package.json中添加,在publish的时候,可以将两个目录都发布
js
"files": [
    "lib",
    "bin"
]
  1. npm publish发布成功 csf-gulp-build
  2. yarn publish --registry https://registry.yarnpkg.comyarn发布到yarn的镜像源,和npm是同步的。

使用

  1. 创建一个新的文件夹csf-gulp-demo,将 public/src/pages.config.js复制进去
  2. npm init -y创建一个package.json文件。
  3. npm i csf-gulp-build --save-dev
  4. csf-gulp-build build即可进行build发布
  5. 还可以在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包,直接使用

MIT Licensed