实现vue-cli(一):gulp自动化构建

一、大概思路

一般自动化构建做的主要是以下几件事情:

(一)开发阶段的构建
  1. 将scss转成css,并在临时目录下生成对应的css文件(在开发阶段不需要压缩,所以将压缩放在单独的步骤里)。
  2. 将es6等浏览器不支持的新特性用babel转成浏览器支持的js语法,并生成文件。
  3. 根据提供的html模板(主要做的是数据填充)生成对应的html代码和文件。
  4. 启动一个服务器打开页面,并监听上面三步和图片等文件变化,若有变化则刷新页面。
(二)发布阶段的构建
  1. ...前三个步骤和开发阶段一样,都是处理html、js、css。
  2. 清空构建目录下的所有旧文件。
  3. 压缩字体文件和图片文件资源到构建目录下。
  4. 将public里不需要处理的资源,直接拷贝生成到构建目录下。
  5. 将前三步临时目录里的css、js、html文件做压缩混淆合并处理,并生成对应文件到构建目录下,将处理后的生成css和js文件插入到html里。

注意前三步的编译都是在临时目录下,最后压缩混淆合并才在构建目录下。之所以分两个目录,是因为在第7步对同个目录边读边写会产生冲突。

二、“开发阶段构建任务”具体实现

(一)准备工作
  1. 请确保自己本地已有npm或yarn等包管理工具,本文用yarn做演示。
  2. 下载该演示项目,或者自行vue-cli创建一个项目。
  3. 在该文件夹下空白处“按shift+鼠标右键”,选中“在此处打开命令行/powershell窗口”打开命令行窗口,或者自行通过命令窗口cd到该文件目录下。
  4. 命令行输入yarn init回车,自行填写信息一路回车,最后生成package.json配置文件。
  5. 命令行输入yarn add gulp --dev安装gulp自动构建工具。
  6. 在演示项目下的gulpfile.js输入下面代码(gulpfile.js是gulp的运行文件)。
const { src, dest } = require("gulp")

const extra = () => {
  // src是gulp中读取文件的方法,dest是gulp中写入文件到目标位置的方法
  // 将public目录下,以public为根目录的所有文件拷贝到dist目录下
  return src('public/**', {base: 'public'})
    .pipe(dest('dist'))
}

module.exports = {
  extra,
}
  1. 命令行输入yarn gulp extra(extra是gulpfile.js对应的函数任务名),如果能将public下文件拷贝到dist则证明成功。
(二)css处理
  1. 由于需要依赖到node-sass国外源可能会出现安装失败的问题,建议先设置npm的安装源为国内的淘宝镜像npm config set registry http://registry.npm.taobao.org
  2. 命令行输入yarn add gulp-sass sass --dev安装gulp处理sass的插件。
  3. gulpfile.js文件输入以下代码,再执行yarn gulp style成功生成temp对应css文件则成功。
const { src, dest } = require("gulp")
const sass = require('gulp-sass')

const style = () => {
  return src('src/assets/styles/*.scss', {base: 'src'})
    .pipe(sass({outputStyle: 'expanded'})) // _开头的sass文件被认为是依赖文件,不会被转换。expanded可以让右括号换行而不是跟在分号后面
    .pipe(dest('temp'))
}

module.exports = {
  style,
}

由于后续需要加载比较多的gulp插件,需要频繁写require,此处采用一个gulp-load-plugins来自动加载所需插件,命令行输入yarn add gulp-load-plugins --dev,代码改写为如下

const { src, dest } = require("gulp")
const loadPlugins = require('gulp-load-plugins')
const plugins = loadPlugins()

const style = () => {
  return src('src/assets/styles/*.scss', {base: 'src'})
    .pipe(plugins.sass({outputStyle: 'expanded'})) // _开头的sass文件被认为是依赖文件,不会被转换。expanded可以让右括号换行而不是跟在分号后面
    .pipe(dest('temp'))
}

module.exports = {
  style,
}
(三)js处理
  1. 命令行输入yarn add gulp-babel @babel/core @babel/preset-env --dev安装gulp转码js的插件。
  2. gulpfile.js文件增加以下代码,再执行yarn gulp script成功生成temp对应已转码好的js文件则成功。
const script = () => {
  return src('src/assets/scripts/*.js', {base: 'src'})
    .pipe(plugins.babel({presets: ['@babel/preset-env']}))
    .pipe(dest('temp'))
}

module.exports = {
  ...,
  script,
}
(四)html处理
  1. 命令行输入yarn add gulp-swig --dev安装gulp启动服务器并监听文件变化的插件。
  2. gulpfile.js文件增加以下代码,再执行yarn gulp page成功生成temp对应已插入好数据的html文件则成功。
const data = {
  menu: [],
  pkg: require('./package.json'),
  date: new Date(),
}

const page = () => {
  return src('src/*.html', {base: 'src'})
    .pipe(plugins.swig({data, defaults: {cache: false}})) // 指定页面中的插值data,根据模板生成html。记得忽略缓存,否则可能更新失败
    .pipe(dest('temp'))
    .pipe(bs.reload({stream: true}))
}

module.exports = {
  ...,
  page,
}
(五)服务器上启动页面并监听变化
  1. 命令行输入yarn add browser-sync --dev安装gulp对页面模板进行编译的插件。
  2. gulpfile.js文件增加以下代码,再执行yarn gulp serve,成功在浏览器启动页面,并且修改sass/js/html时会同步刷新页面变化则成功。
const { src, dest, watch } = require("gulp")
const browserSync = require('browser-sync') // 不是gulp插件所以要自己导入
const bs = browserSync.create() // 创建服务器

const serve = () => {
  // 由于bs.init只监听了temp下已经编译好文件的变化,但我们真正编辑是在src下的未处理文件,所以要先监听src文件变化触发编译到temp触发bs监听
  watch('src/assets/styles/*.scss', style)
  watch('src/assets/scripts/*.js', script)
  watch('src/*.html', page)
  // 图片等资源不在temp目录下,监听到变化了要手动调用bs.reload
  watch([
    'src/assets/images/**',
    'src/assets/fonts/**',
    'public/**',
  ], bs.reload)

  bs.init({
    notify: false, // 不提示刷新
    port: 2000,
    open: true, // 是否自动打开浏览器
    files: 'temp/**', // 哪些文件需要监听修改
    server: { // 先根据routes配置去指定目录找文件,找不到再根据baseDir配置去找
      routes: { // 遇到/node_modules路径时去node_modules目录下找文件
        '/node_modules': 'node_modules',
      },
      baseDir: ['temp', 'src', 'public'], // 需要寻找对应文件时,可以先在temp目录下找有没有,没有再找src再public
    },
  })
}

module.exports = {
  ...,
  serve,
}
(六)整合为开发阶段构建任务

此处通过gulp的两个方法parallelseries来整合上面四步任务。parallel方法可以同时开始执行任务,series方法是按顺序先后执行任务。整合代码如下,命令行yarn gulp develop即可运行该任务。

const compile = parallel(style, script, page) // 因为三个任务互不影响所以用parallel
const develop = series(compile, serve) // 要先编译才有temp目录,才能监听变化,所以用series

module.exports = {
  compile,
  develop,
}

三、“发布阶段构建任务”具体实现

(一)清空构建目录
  1. 命令行输入yarn add del --dev安装删除文件的插件。
  2. gulpfile.js文件增加以下代码,再执行yarn gulp clean,成功删除目录下的指定文件则成功。
const del = require('del')

const clean = () => {
  return del(['dist']) // del返回的是一个promise
}

module.exports = {
  clean,
}
(二)压缩图片和字体
  1. 命令行输入yarn add gulp-imagemin --dev安装压缩图片的插件。
  2. gulpfile.js文件增加以下代码,再执行yarn gulp image font,生成了对应图片并比原文件小则成功。
const image = () => {
  return src('src/assets/images/**', {base: 'src'}) // **任意文件
    .pipe(plugins.imagemin())
    .pipe(dest('dist'))
}

const font = () => { // 字体也有svg可以用同样压缩
  return src('src/assets/fonts/**', {base: 'src'})
     .pipe(plugins.imagemin())
     .pipe(dest('dist'))
 }

module.exports = {
  image,
  font,
}
(三)拷贝public资源

这段在开头准备工作中其实已经讲过,直接复制代码过来,不再赘述。

const extra = () => {
  // src是gulp中读取文件的方法,dest是gulp中写入文件到目标位置的方法
  // 将public目录下,以public为根目录的所有文件拷贝到dist目录下
  return src('public/**', {base: 'public'})
    .pipe(dest('dist'))
}
(四)压缩处理js/css/html,并将处理后的js/css插入html
  1. 命令行输入yarn add gulp-useref --dev安装处理引用关系的插件,可以将类似如下的特定注释内的引用文件打包合并到指定文件中。(这段代码表示把bootstrap.css和test.css两个文件打包合并成assets/styles/目录下一个vendor.css文件)
<!-- build:css assets/styles/vendor.css -->
<link rel="stylesheet" href="/node_modules/bootstrap/dist/css/bootstrap.css">
<link rel="stylesheet" href="/node_modules/test/test.css">
<!-- endbuild -->
  1. 命令行输入yarn add gulp-htmlmin gulp-uglify gulp-clean-css --dev安装压缩处理html/js/css的gulp插件,yarn add gulp-if --dev安装用于判断当前文件类型并做对应处理的gulp插件。
  2. gulpfile.js文件增加以下代码,再执行yarn gulp useref,生成了对应各压缩文件并插入到html则成功。
const useref = () => {
   return src('temp/*.html', {base: 'temp'})
    .pipe(plugins.useref({searchPath: ['temp', '.']})) // 指定html里写的引入文件去哪里找
    // 找到的html、js、css文件分别做不同的处理
    .pipe(plugins.if(/\.js$/, plugins.uglify()))
    .pipe(plugins.if(/\.css$/, plugins.cleanCss()))
    .pipe(plugins.if(/\.html$/, plugins.htmlmin({
      collapseWhitespace: true,
      minifyCSS: true, // 对html里的css代码做压缩
      minifyJS: true,
    })))
    .pipe(dest('dist'))
 }

module.exports = {
  ...,
  useref,
}
(五)整合为发布阶段构建任务

由于useref执行完一次之后,temp里的html已经被压缩,特定注释被删除,所以再次运行useref任务无法达到想要的效果,所以必须先编译compile生成注释代码,再执行useref才有效。整合代码如下,命令行yarn gulp build即可运行该任务。

// 先删除旧目录,再执行构建任务生成对应文件
const build = series(clean, parallel(series(compile, useref), image, font, extra))

module.exports = {
  compile,
  develop,
  build,
}

四、最终gulpfile.js代码

const { src, dest, watch, parallel, series } = require("gulp")
const loadPlugins = require('gulp-load-plugins')
const plugins = loadPlugins()
const browserSync = require('browser-sync') // 不是gulp插件(gulp-开头)所以要自己导入
const bs = browserSync.create() // 创建服务器
const del = require('del')
const data = {
  menu: [],
  pkg: require('./package.json'),
  date: new Date(),
}

const style = () => {
  return src('src/assets/styles/*.scss', {base: 'src'})
    .pipe(plugins.sass({outputStyle: 'expanded'})) // _开头的sass文件被认为是依赖文件,不会被转换。expanded可以让右括号换行而不是跟在分号后面
    .pipe(dest('temp'))
    // .pipe(bs.reload({stream: true})) // 如果bs初始化files项没配置,也可以在watch到之后在任务里reload
}

const script = () => {
  return src('src/assets/scripts/*.js', {base: 'src'})
    .pipe(plugins.babel({presets: ['@babel/preset-env']}))
    .pipe(dest('temp'))
    // .pipe(bs.reload({stream: true}))
}

const page = () => {
  return src('src/*.html', {base: 'src'})
    .pipe(plugins.swig({data, defaults: {cache: false}})) // 指定页面中的插值data,根据模板生成html。记得忽略缓存,否则可能更新失败
    .pipe(dest('temp'))
    // .pipe(bs.reload({stream: true}))
}

const serve = () => {
  // 由于bs.init只监听了temp下已经编译好文件的变化,但我们真正编辑是在src下的未处理文件,所以要先监听src文件变化触发编译到temp触发bs监听
  watch('src/assets/styles/*.scss', style)
  watch('src/assets/scripts/*.js', script)
  watch('src/*.html', page)
  // 图片等资源不在temp目录下,监听到变化了要手动调用bs.reload
  watch([
    'src/assets/images/**',
    'src/assets/fonts/**',
    'public/**',
  ], bs.reload)

  bs.init({
    notify: false, // 不提示刷新
    port: 2000,
    open: true, // 是否自动打开浏览器
    files: 'temp/**', // 哪些文件需要监听修改
    server: { // 先根据routes配置去指定目录找文件,找不到再根据baseDir配置去找
      routes: { // 遇到/node_modules路径时去node_modules目录下找文件
        '/node_modules': 'node_modules',
      },
      baseDir: ['temp', 'src', 'public'], // 需要寻找对应文件时,可以先在temp目录下找有没有,没有再找src再public
    },
  })
}

const clean = () => {
  return del(['dist']) // del返回的是一个promise
}

const image = () => {
  return src('src/assets/images/**', {base: 'src'}) // **任意文件
    .pipe(plugins.imagemin())
    .pipe(dest('dist'))
}

const font = () => { // 字体也有svg可以用同样压缩
  return src('src/assets/fonts/**', {base: 'src'})
    .pipe(plugins.imagemin())
    .pipe(dest('dist'))
 }

 const extra = () => {
   // src是gulp中读取文件的方法,dest是gulp中写入文件到目标位置的方法
   // 将public目录下,以public为根目录的所有文件拷贝到dist目录下
   return src('public/**', {base: 'public'})
     .pipe(dest('dist'))
 }

 const useref = () => {
   return src('temp/*.html', {base: 'temp'})
    .pipe(plugins.useref({searchPath: ['temp', '.']})) // 指定html里写的引入文件去哪里找
    // 找到的html、js、css文件分别做不同的处理
    .pipe(plugins.if(/\.js$/, plugins.uglify()))
    .pipe(plugins.if(/\.css$/, plugins.cleanCss()))
    .pipe(plugins.if(/\.html$/, plugins.htmlmin({
      collapseWhitespace: true,
      minifyCSS: true, // 对html里的css代码做压缩
      minifyJS: true,
    })))
    .pipe(dest('dist'))
 }

const compile = parallel(style, script, page) // 因为三个任务互不影响所以用parallel
const develop = series(compile, serve) // 要先编译才有temp目录,才能监听变化,所以用series
// 先删除旧目录,再执行构建任务生成对应文件
const build = series(clean, parallel(series(compile, useref), image, font, extra))

module.exports = {
  compile,
  develop,
  build,
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容