开发脚手架及封装自动化构建工作流

175 阅读13分钟

前端工程化

  1. 概念
  • 通过工具提高效率,降低成本、质量保证的一种手段(一切重复的工作都应该被自动化)
  • 前端工程化由Node.js强力驱动的
  1. 解决的问题 :
  • 传统语言和语法的弊端:
    • 使用Sass/Less/PostCss增加CSS的编程性,但是运行环境不能直接支持
    • 想要使用ES6,但是有兼容问题
  • 使用组件化/模块化时,运行环境不能直接支持
  • 重复的机械工作
    • 部署上线前需要手动压缩代码及资源文件,及部署过程中要手动上传服务器
  • 代码风格统一、质量保证
  • 部分功能需要等待后端接口支持
  • 整体依赖后端项目

脚手架工具

介绍

创建项目基础结构、提供项目的规范和约定

  • 相同的组织结构
  • 相同的开发范式
  • 相同的模块依赖
  • 相同的工具配置
  • 相同的基础代码

常用的脚手架工具及使用

vue-cli、angular-cli、create-react-app

yarn global add yo
  • 安装对应的generator
yarn global add generator-node
  • 通过yo运行generator
yo node

Yeoman

  1. 特点:
  • 通用性项目脚手架工具
  • 更像是一个脚手架运行平台,搭配不同generator创建任何类型的项目,可以通过创建自己的generator,定义自己的前端脚手架
  1. 缺点:太过通用,不够专注,优点也是缺点
  2. 基本使用:
  • 在全局安装yo
  1. sub generator

在已有的项目上去创建一些特定类型的文件(eg:eslint、babel等)

  • 创建sub generator
yo node:cli
  1. 自定义generator
  • 创建文件夹generator-my(命名必须以 generator-[name]的形式)
  • 创建以下目录结构 image.png
  • 模板的目录结构 image.png
  • 安装基类
yarn add yeoman-generator
  • 在generators/app/index.js中写入内容
// 此文件作为 Generator 的核心入口
// 需要导出一个继承自 Yeoman Generator 的类型
// Yeoman Generator 在工作时会自动调用我们在此类型中定义的一些生命周期方法
// 我们在这些方法中可以通过调用父类提供的一些工具方法实现一些功能,例如文件写入

const Generator = require('yeoman-generator')

module.exports = class extends Generator {
  //命令行交互
  prompting () {
    // Yeoman 在询问用户环节会自动调用此方法
    // 在此方法中可以调用父类的 prompt() 方法发出对用户的命令行询问
    return this.prompt([
      {
        type: 'input',
        name: 'name',
        message: 'Your project name',
        default: this.appname // appname 为项目生成目录名称
      }
    ])
    .then(answers => {
      // answers => { name: 'user input value' }
      this.answers = answers
    })
  }
  writing () {
    // 1.Yeoman 自动在生成文件阶段调用此方法

    // // 我们这里尝试往项目目录中写入文件
    // this.fs.write(
    //   this.destinationPath('temp.txt'),
    //   Math.random().toString()
    // )

    // -------------------------------------------------------

    // // 2. 通过模板方式写入文件到目标目录 EJS 

    // // 模板文件路径
    // const tmpl = this.templatePath('foo.txt')
    // // 输出目标路径
    // const output = this.destinationPath('foo.txt')
    // // 模板数据上下文
    // const context = { title: 'Hello zce~', success: false }

    // this.fs.copyTpl(tmpl, output, context)

    // -------------------------------------------------------

    //3.
    // 模板文件路径
    const tmpl = this.templatePath('bar.html')
    // 输出目标路径
    const output = this.destinationPath('bar.html')
    // 模板数据上下文
    const context = this.answers

    this.fs.copyTpl(tmpl, output, context)
  }
}
  • 映射、运行
yarn link
yo my
  1. 自定义generator-vue
const Generator = require('yeoman-generator')

module.exports = class extends Generator {
  prompting () {
    return this.prompt([
      {
        type: 'input',
        name: 'name',
        message: 'Your project name',
        default: this.appname
      }
    ])
    .then(answers => {
      this.answers = answers
    })
  }

  writing () {
    // 把每一个文件都通过模板转换到目标路径

    const templates = [
      '.browserslistrc',
      '.editorconfig',
      '.env.development',
      '.env.production',
      '.eslintrc.js',
      '.gitignore',
      'babel.config.js',
      'package.json',
      'postcss.config.js',
      'README.md',
      'public/favicon.ico',
      'public/index.html',
      'src/App.vue',
      'src/main.js',
      'src/router.js',
      'src/assets/logo.png',
      'src/components/HelloWorld.vue',
      'src/store/actions.js',
      'src/store/getters.js',
      'src/store/index.js',
      'src/store/mutations.js',
      'src/store/state.js',
      'src/utils/request.js',
      'src/views/About.vue',
      'src/views/Home.vue'
    ]

    templates.forEach(item => {
      // item => 每个文件路径
      this.fs.copyTpl(
        this.templatePath(item),
        this.destinationPath(item),
        this.answers
      )
    })
  }
}
  1. 发布generator

plop

作用:在项目开发的过程中,创建多个有基础代码的文件 使用:

  • 安装
yarn add plop --dev
  • 创建plopfile.js文件
  • 创建模板文件 image.png
  • 在plopfile.js中写入
// Plop 入口文件,需要导出一个函数
// 此函数接收一个 plop 对象,用于创建生成器任务

module.exports = plop => {
  plop.setGenerator('component', {
    description: 'create a component',
    prompts: [  //命令行交互
      {
        type: 'input',
        name: 'name',
        message: 'component name',
        default: 'MyComponent'
      }
    ],
    actions: [ //完成命令行交互之后执行
      {
        type: 'add', // 代表添加文件
        path: 'src/components/{{name}}/{{name}}.js',
        templateFile: 'plop-templates/component.hbs'
      },
      {
        type: 'add', // 代表添加文件
        path: 'src/components/{{name}}/{{name}}.css',
        templateFile: 'plop-templates/component.css.hbs'
      },
      {
        type: 'add', // 代表添加文件
        path: 'src/components/{{name}}/{{name}}.test.js',
        templateFile: 'plop-templates/component.test.hbs'
      }
    ]
  })
}

通过node创建脚手架

  • 创建文件夹 sample-scaffolding ,yarn init生成初始配置
  • 在package.json中在bin字段指定cli应用的入口文件 image.png
  • 创建模板文件夹templates
  • 创建cli.js文件,写入
#!/usr/bin/env node

// Node CLI 应用入口文件必须要有这样的文件头
// 如果是 Linux 或者 macOS 系统下还需要修改此文件的读写权限为 755
// 具体就是通过 chmod 755 cli.js 实现修改

// 脚手架的工作过程:
// 1. 通过命令行交互询问用户问题
// 2. 根据用户回答的结果生成文件

const fs = require('fs')
const path = require('path')
const inquirer = require('inquirer')
const ejs = require('ejs')

inquirer.prompt([
  {
    type: 'input',
    name: 'name',
    message: 'Project name?'
  }
])
.then(anwsers => {
  // console.log(anwsers)
  // 根据用户回答的结果生成文件

  // 模板目录
  const tmplDir = path.join(__dirname, 'templates')  //_dirname获取的是该文件的绝对目录
  // 目标目录
  const destDir = process.cwd()   //命令行工作目录

  // 将模板下的文件全部转换到目标目录
  fs.readdir(tmplDir, (err, files) => {
    if (err) throw err
    files.forEach(file => {
      // 通过模板引擎渲染文件
      ejs.renderFile(path.join(tmplDir, file), anwsers, (err, result) => {
        if (err) throw err

        // 将结果写入目标文件路径
        fs.writeFileSync(path.join(destDir, file), result)
      })
    })
  })
})
  • 通过 yarn link 的方式映射到全局
  • 最后我们就可以通过命令行工具使用 sample-scaffolding

自动化构建

特点: 将运行环境的代码转化为生产环境,脱离运行环境兼容带来的问题,让我们可以在开发阶段使用提高效率的语法、规范和标准(eg:ES6、Sass、模板引擎) browser-sync

常用的构建工具

Grunt

  1. 特点:

插件生态完善 工作过程是基于临时文件实现,构建过程较慢

  1. 基本使用:
  • 安装
yarn add grunt
  • 创建gruntFile.js文件,写入
// Grunt 的入口文件
// 用于定义一些需要 Grunt 自动执行的任务
// 需要导出一个函数
// 此函数接收一个 grunt 的对象类型的形参
// grunt 对象中提供一些创建任务时会用到的 API

module.exports = grunt => {
  grunt.registerTask('foo', 'a sample task', () => {
    console.log('hello grunt')
  })

  grunt.registerTask('bar','任务描述', () => {
    console.log('other task')
  })

  // // default 是默认任务名称
  // // 通过 grunt 执行时可以省略
  // grunt.registerTask('default', () => {
  //   console.log('default task')
  // })

  // 第二个参数可以指定此任务的映射任务,
  // 这样执行 default 就相当于执行对应的任务
  // 这里映射的任务会按顺序依次执行,不会同步执行
  grunt.registerTask('default', ['foo', 'bar'])

  // 也可以在任务函数中执行其他任务
  grunt.registerTask('run-other', () => {
    // foo 和 bar 会在当前任务执行完成过后自动依次执行
    grunt.task.run('foo', 'bar')
    console.log('current task runing~')
  })

  // 默认 grunt 采用同步模式编码
  // 如果需要异步可以使用 this.async() 方法创建回调函数
  // grunt.registerTask('async-task', () => {
  //   setTimeout(() => {
  //     console.log('async task working~')
  //   }, 1000)
  // })

  // 由于函数体中需要使用 this,所以这里不能使用箭头函数
  grunt.registerTask('async-task', function () {
    const done = this.async()
    setTimeout(() => {
      console.log('async task working~')
      done()
    }, 1000)
  })
}


  • 执行任务 eg
yarn grunt foo
  1. 深入
  • 通过return false标记任务失败,后续任务都不会执行,异步任务通过done(false)标记
module.exports = grunt => {
  // 任务函数执行过程中如果返回 false
  // 则意味着此任务执行失败
  grunt.registerTask('bad', () => {
    console.log('bad working~')
    return false
  })

  grunt.registerTask('foo', () => {
    console.log('foo working~')
  })

  grunt.registerTask('bar', () => {
    console.log('bar working~')
  })

  // 如果一个任务列表中的某个任务执行失败
  // 则后续任务默认不会运行
  // 除非 grunt 运行时指定 --force 参数强制执行
  grunt.registerTask('default', ['foo', 'bad', 'bar'])

  // 异步函数中标记当前任务执行失败的方式是为回调函数指定一个 false 的实参
  grunt.registerTask('bad-async', function () {
    const done = this.async()
    setTimeout(() => {
      console.log('async task working~')
      done(false)
    }, 1000)
  })
}
  • 通过 --force可以强制执行标记失败后的任务
yarn grunt default --force
  • 为任务添加一些配置选项
module.exports = grunt => {
  // grunt.initConfig() 用于为任务添加一些配置选项
  grunt.initConfig({
    // 键一般对应任务的名称
    // 值可以是任意类型的数据
    foo: {
      bar: 'baz'
    }
  })

  grunt.registerTask('foo', () => {
    // 任务中可以使用 grunt.config() 获取配置
    console.log(grunt.config('foo'))
    // 如果属性值是对象的话,config 中可以使用点的方式定位对象中属性的值
    console.log(grunt.config('foo.bar'))
  })
}
  • 多目标任务配置
module.exports = grunt => {
  // 多目标模式,可以让任务根据配置形成多个子任务

  // grunt.initConfig({
  //   build: {
  //     foo: 100,
  //     bar: '456'
  //   }
  // })

  // grunt.registerMultiTask('build', function () {
  //   console.log(`task: build, target: ${this.target}, data: ${this.data}`)
  // })

  grunt.initConfig({
    build: {
      options: { //配置选项
        msg: 'task options'
      },
      foo: {
        options: {
          msg: 'foo target options'
        }
      },
      bar: '456'
    }
  })

  grunt.registerMultiTask('build', function () {
    console.log(this.options())
  })
}

  • grunt插件的使用
    • 安装grunt-contrib-clean
    • 写入gruntfile.js文件
module.exports = grunt => {
  grunt.initConfig({
    clean: {
      temp: 'temp/**'
    }
  })
  
  grunt.loadNpmTasks('grunt-contrib-clean')
}
  • 常用grunt插件的使用
const sass = require('sass')
const loadGruntTasks = require('load-grunt-tasks')

module.exports = grunt => {
  grunt.initConfig({
    sass: {
      options: {
        sourceMap: true,
        implementation: sass
      },
      main: {
        files: {
          'dist/css/main.css': 'src/scss/main.scss'
        }
      }
    },
    babel: {
      options: {
        sourceMap: true,
        presets: ['@babel/preset-env']
      },
      main: {
        files: {
          'dist/js/app.js': 'src/js/app.js'
        }
      }
    },
    watch: {
      js: {
        files: ['src/js/*.js'],
        tasks: ['babel']
      },
      css: {
        files: ['src/scss/*.scss'],
        tasks: ['sass']
      }
    }
  })

  // grunt.loadNpmTasks('grunt-sass')
  loadGruntTasks(grunt) // 自动加载所有的 grunt 插件中的任务

  grunt.registerTask('default', ['sass', 'babel', 'watch'])
}

Gulp

  1. 特点:

插件生态完善 基于内存实现 使用简单

  1. 基本使用
  • 安装
yarn add gulp --dev
  • 创建gulpfile.js,写入
// 导出的函数都会作为 gulp 任务
// exports.foo = () => {
//   console.log('foo task working~')
// }

// gulp 的任务函数都是异步的
// 可以通过调用回调函数标识任务完成
exports.foo = done => {
  console.log('foo task working~')
  done() // 标识任务执行完成
}

// default 是默认任务
// 在运行是可以省略任务名参数
exports.default = done => {
  console.log('default task working~')
  done()
}
  • 执行
yarn gulp foo
  1. 组合任务的创建
const { series, parallel } = require('gulp')

const task1 = done => {
  setTimeout(() => {
    console.log('task1 working~')
    done()
  }, 1000)
}

const task2 = done => {
  setTimeout(() => {
    console.log('task2 working~')
    done()
  }, 1000)  
}

const task3 = done => {
  setTimeout(() => {
    console.log('task3 working~')
    done()
  }, 1000)  
}

// 让多个任务按照顺序依次执行
exports.foo = series(task1, task2, task3)

// 让多个任务同时执行
exports.bar = parallel(task1, task2, task3)
  1. 处理异步任务的三种方式, 以及错误优先,阻止后续任务的执行,以及文件流
const fs = require('fs')

exports.callback = done => {
  console.log('callback task')
  done()
}

exports.callback_error = done => {
  console.log('callback task')
  done(new Error('task failed'))
}

exports.promise = () => {
  console.log('promise task')
  return Promise.resolve()
}

exports.promise_error = () => {
  console.log('promise task')
  return Promise.reject(new Error('task failed'))
}

const timeout = time => {
  return new Promise(resolve => {
    setTimeout(resolve, time)
  })
}

exports.async = async () => {
  await timeout(1000)
  console.log('async task')
}

exports.stream = () => {
  const read = fs.createReadStream('yarn.lock')
  const write = fs.createWriteStream('a.txt')
  read.pipe(write)
  return read
}

// exports.stream = done => {
//   const read = fs.createReadStream('yarn.lock')
//   const write = fs.createWriteStream('a.txt')
//   read.pipe(write)
//   read.on('end', () => {
//     done()
//   })
// }

  1. gulp构建核心原理(流)

  2. gulp传统文件操作

const fs = require('fs')
const { Transform } = require('stream')

exports.default = () => {
  // 文件读取流
  const readStream = fs.createReadStream('normalize.css')

  // 文件写入流
  const writeStream = fs.createWriteStream('normalize.min.css')

  // 文件转换流
  const transformStream = new Transform({
    // 核心转换过程
    transform: (chunk, encoding, callback) => {
      const input = chunk.toString()
      const output = input.replace(/\s+/g, '').replace(/\/\*.+?\*\//g, '')
      callback(null, output)
    }
  })

  return readStream
    .pipe(transformStream) // 转换
    .pipe(writeStream) // 写入
}

  1. gulp api 文件操作 以及 cleanCss rename 插件使用
const { src, dest } = require('gulp')
const cleanCSS = require('gulp-clean-css') //压缩css
const rename = require('gulp-rename')   // 写入文件命名

exports.default = () => {
  return src('src/*.css')
    .pipe(cleanCSS())
    .pipe(rename({ extname: '.min.css' }))
    .pipe(dest('dist'))
}

  1. 样式编译
  • 需要安装 gulp-sass
  • _*.scss的依赖文件不会被转换
const { src, dest } = require('gulp')
const sass=require('gulp-sass')
const style = () => {
  return src('src/assets/styles/*.scss', { base: 'src' }) //base 以基础路径保存
    .pipe(sass({ outputStyle: 'expanded' }))  // outputStyle: 'expanded'展开输出
    .pipe(dest('dist'))
}
moudle.exports={
  style
}
  1. 脚本编译
  • yarn add gulp-babel @babel/core @babel/preset-env --dev
const { src, dest } = require('gulp')
const babel=require('gulp-babel')
const script = () => {
  return src('src/assets/scripts/*.js', { base: 'src' }) //base 以基础路径保存
    .pipe(babel({presets:['@babel/preset-env']}))  
    .pipe(dest('dist'))
}
moudle.exports={
  script
}
  1. html编译
  • yarn add gulp-swig --dev
const { src, dest } = require('gulp')
const swig=require('gulp-swig')

const data = {   //解决模板数据标记
  menus: [
    {
      name: 'Home',
      icon: 'aperture',
      link: 'index.html'
    },
    {
      name: 'Features',
      link: 'features.html'
    },
    {
      name: 'About',
      link: 'about.html'
    },
    {
      name: 'Contact',
      link: '#',
      children: [
        {
          name: 'Twitter',
          link: 'https://twitter.com/w_zce'
        },
        {
          name: 'About',
          link: 'https://weibo.com/zceme'
        },
        {
          name: 'divider'
        },
        {
          name: 'About',
          link: 'https://github.com/zce'
        }
      ]
    }
  ],
  pkg: require('./package.json'),
  date: new Date()
}

const page = () => {
  return src('src/*.html', { base: 'src' }) //base 以基础路径保存
    .pipe(swig({data, defaults: { cache: false }})) // 防止模板缓存导致页面不能及时更新 
    .pipe(dest('dist'))
}
moudle.exports={
  page
}
  1. 图片和字体转换
  • yarn add gulp-imagemin --dev
const { src, dest } = require('gulp')
const imagemin=require('gulp-imagemin')

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

const font = () => {
  return src('src/assets/fonts/**', { base: 'src' })
    .pipe(imagemin())
    .pipe(dest('dist'))
}

moudle.exports={
  image,
  font
}
  1. 其它不变的文件写入
const { src, dest } = require('gulp')

const extra = () => {
  return src('public/**', { base: 'public' })
    .pipe(dest('dist'))
}

moudle.exports={
  extra
}
  1. 文件清除
  • yarn add del --dev
const del = require('del')

const clean = () => {
  return del(['dist'])
}
moudle.exports={
  clean
}
  1. 自动gulp插件
  • 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' }))
    .pipe(dest('dist'))
    .pipe(bs.reload({ stream: true }))
}

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

const page = () => {
  return src('src/*.html', { base: 'src' })
    .pipe(plugins.swig({ data })) 
    .pipe(dest('dist'))
}

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

const font = () => {
  return src('src/assets/fonts/**', { base: 'src' })
    .pipe(plugins.imagemin())
    .pipe(dest('dist'))
}
  1. 开发服务器 browser-sync
  • yarn add browser-sync --dev
const { src, dest, parallel, series, watch } = require('gulp')

const browserSync = require('browser-sync')
const bs = browserSync.create()

const serve = () => {
  bs.init({
    notify: false,
    port: 2080,
    // open: false,
    // files: 'dist/**', //指定dist下的文件修改后,browsersync自动更新浏览器
    server: {
      baseDir: ['temp', 'src', 'public'],
      routes: {    //routes优先级高于baseDir
        '/node_modules': 'node_modules'  //'/node_modules'映射为node_modules
      }
    }
  })
}
moudle.exports={
  serve
}
  1. 通过watch监听文件的变化,然后构建
  • 可以在将image、font、extra组合在一起 ,通过bs.reload刷新游览器
  • 通过files和watch的方式实现热更新
  • 也可以去掉files, 在 style、script、page任务下 以流的形式bs.reload往游览器中推实现热更新
const { src, dest, parallel, series, watch } = require('gulp')

const browserSync = require('browser-sync')
const bs = browserSync.create()

const style = () => {
  return src('src/assets/styles/*.scss', { base: 'src' })
    .pipe(plugins.sass({ outputStyle: 'expanded' }))
    .pipe(dest('temp'))
    .pipe(bs.reload({ stream: true }))
}

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 } })) // 防止模板缓存导致页面不能及时更新
    .pipe(dest('temp'))
    .pipe(bs.reload({ stream: true }))
}

const serve = () => {
  watch('src/assets/styles/*.scss', style)
  watch('src/assets/scripts/*.js', script)
  watch('src/*.html', page)
  // watch('src/assets/images/**', image)
  // watch('src/assets/fonts/**', font)
  // watch('public/**', extra)
  watch([
    'src/assets/images/**',
    'src/assets/fonts/**',
    'public/**'
  ], bs.reload)
  
  bs.init({
    notify: false,
    port: 2080,
    // open: false,
    // files: 'dist/**', //指定dist下的文件修改后,browsersync自动更新浏览器
    server: {
      baseDir: ['dist', 'src', 'public'],
      routes: {    //routes优先级高于baseDir
        '/node_modules': 'node_modules'  //'/node_modules'映射为node_modules
      }
    }
  })
}
moudle.exports={
  serve
}
  1. useref文件引用处理

image.png 

  • 可以将图中注释包裹的引用js文件合并成一个文件输出
  • 安装 yarn add gulp-useref --dev
  • yarn add gulp-htmlmin --dev 压缩html
  • yarn add gulp-uglify--dev 压缩js
  • yarn add gulp-cleanCss--dev 压缩html
  • yarn add gulp-if 区分对应的文件流进行操
  • 将读取和写入文件夹区分: 读取temp 写入 dist (上面 clean、style、script、page输出跟着改变)
const useref = () => {
  return src('temp/*.html', { base: 'temp' })
    .pipe(plugins.useref({ searchPath: ['temp', '.'] }))
    // 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,
      minifyJS: true
    })))
    .pipe(dest('dist'))
}

const clean = () => {
  return del(['dist', 'temp'])
}

const style = () => {
  return src('src/assets/styles/*.scss', { base: 'src' })
    .pipe(plugins.sass({ outputStyle: 'expanded' }))
    .pipe(dest('temp'))
    .pipe(bs.reload({ stream: true }))
}

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 } })) // 防止模板缓存导致页面不能及时更新
    .pipe(dest('temp'))
    .pipe(bs.reload({ stream: true }))
}

const image = () => {
  return src('src/assets/images/**', { base: 'src' })
    .pipe(plugins.imagemin())
    .pipe(dest('dist'))
}
  1. 规划任务执行顺序
const compile = parallel(style, script, page)

// 上线之前执行的任务
const build =  series(
  clean,
  parallel(
    series(compile, useref),
    image,
    font,
    extra
  )
)

//开发时执行的任务
const develop = series(compile, serve)
  1. 全部代码 以及 最后处理
  • .gitgnore中忽略 dist 、temp文件夹
  • 可以将运行gulp的命令放在 npm script中
const { src, dest, parallel, series, watch } = require('gulp')

const del = require('del')
const browserSync = require('browser-sync')

const loadPlugins = require('gulp-load-plugins')

const plugins = loadPlugins()
const bs = browserSync.create()

const data = {
  menus: [
    {
      name: 'Home',
      icon: 'aperture',
      link: 'index.html'
    },
    {
      name: 'Features',
      link: 'features.html'
    },
    {
      name: 'About',
      link: 'about.html'
    },
    {
      name: 'Contact',
      link: '#',
      children: [
        {
          name: 'Twitter',
          link: 'https://twitter.com/w_zce'
        },
        {
          name: 'About',
          link: 'https://weibo.com/zceme'
        },
        {
          name: 'divider'
        },
        {
          name: 'About',
          link: 'https://github.com/zce'
        }
      ]
    }
  ],
  pkg: require('./package.json'),
  date: new Date()
}

const clean = () => {
  return del(['dist', 'temp'])
}

const style = () => {
  return src('src/assets/styles/*.scss', { base: 'src' })
    .pipe(plugins.sass({ outputStyle: 'expanded' }))
    .pipe(dest('temp'))
    .pipe(bs.reload({ stream: true }))
}

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 } })) // 防止模板缓存导致页面不能及时更新
    .pipe(dest('temp'))
    .pipe(bs.reload({ stream: true }))
}

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

const font = () => {
  return src('src/assets/fonts/**', { base: 'src' })
    .pipe(plugins.imagemin())
    .pipe(dest('dist'))
}

const extra = () => {
  return src('public/**', { base: 'public' })
    .pipe(dest('dist'))
}

const serve = () => {
  watch('src/assets/styles/*.scss', style)
  watch('src/assets/scripts/*.js', script)
  watch('src/*.html', page)
  // watch('src/assets/images/**', image)
  // watch('src/assets/fonts/**', font)
  // watch('public/**', extra)
  watch([
    'src/assets/images/**',
    'src/assets/fonts/**',
    'public/**'
  ], bs.reload)

  bs.init({
    notify: false,
    port: 2080,
    // open: false,
    // files: 'dist/**',
    server: {
      baseDir: ['temp', 'src', 'public'],
      routes: {
        '/node_modules': 'node_modules'
      }
    }
  })
}

const useref = () => {
  return src('temp/*.html', { base: 'temp' })
    .pipe(plugins.useref({ searchPath: ['temp', '.'] }))
    // 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,
      minifyJS: true
    })))
    .pipe(dest('dist'))
}

const compile = parallel(style, script, page)

// 上线之前执行的任务
const build =  series(
  clean,
  parallel(
    series(compile, useref),
    image,
    font,
    extra
  )
)

const develop = series(compile, serve)

module.exports = {
  clean,
  build,
  develop
}

  1. 封装gulp 发布
  • 新建一个node 的目录 zce-pages image.png
  • 将上面gulpfile.js的文件放入 lib下的index.js里面
  • 在package.json里面放入引入依赖
    • 要放入dependencies里面 ,在我们安装zce-pages模块时,会自动去安装dependencies下所依赖的模块
  • yarn link 映射本地模块到全局
"dependencies": {
    "@babel/core": "^7.5.5",
    "@babel/preset-env": "^7.5.5",
    "browser-sync": "^2.26.7",
    "del": "^5.1.0",
    "gulp": "^4.0.2",
    "gulp-babel": "^8.0.0",
    "gulp-clean-css": "^4.2.0",
    "gulp-htmlmin": "^5.0.1",
    "gulp-if": "^3.0.0",
    "gulp-imagemin": "^6.1.0",
    "gulp-load-plugins": "^2.0.1",
    "gulp-sass": "^4.0.2",
    "gulp-swig": "^0.9.1",
    "gulp-uglify": "^3.0.2",
    "gulp-useref": "^3.1.6"
  },
  • 创建zce-pages-demo目录,yarn init 初始化 ,通过 yarn link zce-pages引入依赖
  • 创建 gulpfile.js 文件 ,引入 zce-pages
module.exports=reuqire('zce-pages')
  • 最后通过yarn gulp build 来运行
  1. 解决运行data报错, 抽出lib/index.js的data ,并抽出全部引入路径
  • 在实际项目中根目录中创建gulp.config.js文件 ,将data的内容放入其中,并通过module.exports开放
  • 删除lib/index.js文件,写入
const cwd = process.cwd()   //返回命令行所在的工作目录

let config = {
  // default config
  build: {
    src: 'src',
    dist: 'dist',
    temp: 'temp',
    public: 'public',
    paths: {
      styles: 'assets/styles/*.scss',
      scripts: 'assets/scripts/*.js',
      pages: '*.html',
      images: 'assets/images/**',
      fonts: 'assets/fonts/**'
    }
  }
}

try {
  const loadConfig = require(`${cwd}/pages.config.js`)
  config = Object.assign({}, config, loadConfig)
} catch (e) {}

//修改page下data的值
const page = () => {
  return src('src/*.html', { base: 'src' })
    .pipe(plugins.swig({data: config.data, defaults: { cache: false } })) // 防止模板缓存导致页面不能及时更新
    .pipe(dest('temp'))
    .pipe(bs.reload({ stream: true }))
}
  1. 解决@babel/preset-env引入错误问题
  • 通过require的方式引入(require会在index.js所在lib目录中找,找不到就会去父级目录中去找)
//以前   会安装项目的根目录下的node_moudle中找
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 script = () => {
  //cwd: config.build.src  设置开始目录
  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 }))
}
  1. 删除gulpfile.js
  • 删除 gulpfile.js文件
  • 通过 yarn gulp build运行 报错
  • 解决 : 通过--gulpfile指定gulpfile.js的路径
  • 再次运行,发现工作目录变为lib/index.js目录
  • 解决:通过--cwd . 指定工作目录
  • 最终命令yarn gulp build --gulpfile ./node_modules/zce-pages/lib/index.js --cwd .
  1. 包装gulp-cli
  • 在package.json中指定bin路径
 "bin": "bin/zce-pages.js",
  • 创建bin/zce-page.js文件作为 node cli 文件 ,
    • 添加固定头
    • 通过process.arg的方式达到yarn gulp build --gulpfile ./node_modules/zce-pages/lib/index.js --cwd . 的效果
#!/usr/bin/env node 

process.argv.push('--cwd')
process.argv.push(process.cwd())
process.argv.push('--gulpfile')
process.argv.push(require.resolve('..'))

require('gulp/bin/gulp')
  • yarn link映射
  • 进入zce-pages-demo 目录 , 通过zce-pages build 就可以正常运行了,
  1. 发布zce-pages
  • npm push时
    • 会将项目根目录的文件发布上去
    • 还会将 package.json下file字段配置的目录发布上去

FIS

  1. 特点:
  • 捆绑需求集成于内部
  • 资源定位(核心)
  1. 使用
  • 安装
yarn global add fls3
  • 创建fis-conf.js文件,写入
fis.match('*.{js,scss,png}', {
  release: '/assets/$0'
})

fis.match('**/*.scss', {
  rExt: '.css',
  parser: fis.plugin('node-sass'),
  optimizer: fis.plugin('clean-css')
})

fis.match('**/*.js', {
  parser: fis.plugin('babel-6.x'),
  optimizer: fis.plugin('uglify-js')
})

  • 通过命令生成最终文件夹output
fis3 release -d output
``