自动化构建工作流

前端工程化自动化构建

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

# 目标

如果我们有多个差不多的项目,那么每个项目的任务基本都是一样的,那我们就要重复去构建这么多的任务。我们就需要复用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 (opens new window),主要把里面的文件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 (opens new window)文件中的内容拷贝到index.js中
  2. 将gulpfile里面需要的依赖进行copy(在package.json中作为dependencies依赖)
"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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
  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文件中写
// 直接导入node_modules里面的模块
module.exports = require('csf-gulp-build')
1
2
  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的文件,输出出去
module.exports = {
  data: {
    array: [],
    pkg: require('./package.json'),
    date: new Date()
  }
}
1
2
3
4
5
6
7
  1. 原来的地方这么引用
// 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  1. 之后运行报错cannot find module '@babel/preset-env',我们在csf-gulp-build的index.js下面修改script任务
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
2
3
4
5
6
7
8
9
  1. 之后会报imagemin的错误,去csf-gulp-build文件中命令行用cnpm i gulp-imagemin --save-dev重新安装一下gulp-imagemin,之后去Gulpautonpm run build可以启动成功
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
2
3
4
5
6
7
8
9
10
11
12
13
  1. 之后检查一下输出的dist文件,看到html/js/css都被压缩,然后模板语法都有替换,这部分已经没有问题了。

# 路径配置

在原有的基础上,对输入路径和输出路径提供暴露配置接口,这样更加的灵活。

把路径都提取到默认的config中:

// 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
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153

运行npm run buildnpm run serve通过,那么我们在项目page.config.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()
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

运行之后看到输出目录变成了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中

// package.json
"bin": "bin/csf-gulp-build.js"

// bin/csf-gulp-build.js
#!/usr/bin/env node

console.log('csf-gulp-build')
1
2
3
4
5
6
7
  1. 重新将csf-gulp-buiild项目npm link(记得link之前先unlink),运行
csf-gulp-build
# 可以看到正常输出
# csf-gulp-build
1
2
3
  1. 继续编辑bin/csf-gulp-build.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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  1. Gulpauto中运行csf-gulp-build build可以看到正常运行,目录也是正确。这样就不要求本地目录必须添加gulpfile.js了,就可以把gulp全部都包装在csf-gulp-build中了。

# 发布

  1. 在package.json中添加,在publish的时候,可以将两个目录都发布
"files": [
    "lib",
    "bin"
]
1
2
3
4
  1. npm publish发布成功 csf-gulp-build (opens new window)
  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
"scripts": {
    "clean": "csf-gulp-build clean",
    "serve": "csf-gulp-build develop"
}
1
2
3
4

直接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包,直接使用
更新时间: 2021-12-18 20:56