學(xué)習(xí)筆記(八)——自動(dòng)化構(gòu)建

自動(dòng)化構(gòu)建

一切重復(fù)的工作都應(yīng)該被自動(dòng)化

自動(dòng)化構(gòu)建是前端工程化過程中一個(gè)重要的組成部分

自動(dòng)化構(gòu)建工作流可以使我們?cè)陂_發(fā)階段使用一些高效的語法、規(guī)范、標(biāo)準(zhǔn)和工具,脫離運(yùn)行環(huán)境兼容性帶來的問題,并最終自動(dòng)轉(zhuǎn)換成能夠被運(yùn)行環(huán)境支持的可執(zhí)行代碼

自動(dòng)化構(gòu)建初體驗(yàn)

嘗試使用sass開發(fā)頁面樣式,并自動(dòng)編譯為css文件

  • 創(chuàng)建項(xiàng)目目錄,并使用 yarn init -y 初始化

  • 添加index.html頁面用于測(cè)試

  • yarn add sass --dev 添加sass模塊作為開發(fā)依賴

    • 此時(shí)可以使用sass編寫樣式文件*.scss,并使用 yarn sass <source> <target> 命令將scss文件編譯為css文件
  • 在package.json文件中,添加script屬性,定義npm運(yùn)行腳本

    • 此時(shí)可以通過 npm run <command>yarn <command> 運(yùn)行script中定義的命令
  • yarn add npm-run-all --dev 安裝npm-run-all作為開發(fā)依賴,可以用來順序同時(shí)運(yùn)行多個(gè)script中定義的命令

  • yarn add browser-sync --dev 安裝browser-sync作為開發(fā)依賴,可以監(jiān)控指定目錄下的文件改動(dòng),并自動(dòng)刷新瀏覽器

  • 上述步驟實(shí)現(xiàn)了開發(fā)過程中,自動(dòng)將scss文件編譯為css文件,并自動(dòng)監(jiān)聽文件變化,刷新瀏覽器實(shí)時(shí)查看最新頁面效果

    // package.json
    {
      "name": "sample",
      "version": "1.0.0",
      "main": "index.js",
      "license": "MIT",
      "scripts": {
        "build": "sass scss/main.scss css/main.css --watch",
        "serve": "browser-sync . --files \"*.html, css/*.css\"",
        "start": "run-p build serve"
      },
      "devDependencies": {
        "browser-sync": "^2.26.12",
        "sass": "^1.26.10",
        "npm-run-all": "^4.1.5"
      }
    }
    
    

常用的自動(dòng)化構(gòu)建工具

image-20200903162834889
  • Grunt
    • 插件生態(tài)豐富,可以實(shí)現(xiàn)任意類型項(xiàng)目的構(gòu)建
    • 工作過程基于臨時(shí)文件,磁盤讀寫頻繁,構(gòu)建速度較慢
  • Gulp
    • 插件生態(tài)豐富
    • 工作過程基于內(nèi)存,構(gòu)建速度較快
    • 支持同時(shí)進(jìn)行多個(gè)構(gòu)建任務(wù)
  • FIS
    • 由百度前端團(tuán)隊(duì)推出的構(gòu)建工具
    • 捆綁式全家桶
    • 適合新手
  • Webpack?
    • Webpack屬于模塊打包工具

Grunt

  • 基本使用

    • 新建項(xiàng)目目錄,并使用 yarn init -y 命令初始化

    • yarn add grunt 添加grunt模塊

    • 添加gruntfile.js文件

      • Grunt的入口文件
      • 用于定義Grunt自動(dòng)執(zhí)行的任務(wù)
      • 需要導(dǎo)出一個(gè)函數(shù)
      • 函數(shù)接收grunt形參,用于提供創(chuàng)建任務(wù)時(shí)將會(huì)用到的的API
    • grunt.registerTask 注冊(cè)待執(zhí)行的task

      • 第一個(gè)參數(shù)為task的名稱,如果名稱為default,則為默認(rèn)task,執(zhí)行 yarn grunt 不指定task明時(shí)默認(rèn)執(zhí)行 default task
      • 第二個(gè)參數(shù)如果是回調(diào)函數(shù),則表示task執(zhí)行時(shí)要執(zhí)行的內(nèi)容
      • 第二個(gè)參數(shù)如果是字符串,則是對(duì)該task的描述信息,當(dāng)執(zhí)行yarn grunt --help 時(shí)會(huì)顯示相應(yīng)任務(wù)的描述
      • 第二個(gè)參數(shù)如果是數(shù)組,則接收由task名稱組成的字符串?dāng)?shù)組,執(zhí)行該task將會(huì)依次執(zhí)行數(shù)組中指定的task
      • 如果task執(zhí)行的是一個(gè)異步任務(wù),需要使用調(diào)用 this.async() 返回的函數(shù)來標(biāo)記異步操作執(zhí)行完成,此時(shí)grunt會(huì)等待異步操作執(zhí)行完成
    • yarn grunt <task> 執(zhí)行g(shù)runtfile.js中定義的task

      // gruntfile.js
      module.exports = grunt => {
          grunt.registerTask('foo', () => {
              console.log('hello grunt')
          })
      
          grunt.registerTask('bar', 'description', () => {
              console.log('hello bar')
          })
      
          grunt.registerTask('default', ['foo', 'bar'])
      
          grunt.registerTask('async-task', function() {
              const done = this.async();
              setTimeout(()=> {
                  console.log('async task done')
                  done()
              }, 1000)
          })
      }
      
  • 標(biāo)記任務(wù)失敗

    • 同步任務(wù)通過在一個(gè)task的回調(diào)函數(shù)中 return false 來實(shí)現(xiàn)

    • 順序執(zhí)行多個(gè)任務(wù)時(shí),當(dāng)有一個(gè)任務(wù)被標(biāo)記失敗,后續(xù)任務(wù)將不再繼續(xù)執(zhí)行

    • 使用 --force 參數(shù)來強(qiáng)制執(zhí)行所有任務(wù)

    • 異步任務(wù)標(biāo)記失敗,需要使用 this.async() 返回的函數(shù),傳入false

      // gruntfile.js
      module.exports = grunt => {
          grunt.registerTask('foo', () => {
              console.log('hello grunt')
          })
      
          grunt.registerTask('bar', 'description', () => {
              console.log('hello bar')
          })
      
          grunt.registerTask('bad', ()=> {
              console.log('bad task')
              return false
          })
      
          grunt.registerTask('default', ['foo', 'bad', 'bar'])
      
          grunt.registerTask('async-task', function() {
              const done = this.async();
              setTimeout(()=> {
                  console.log('async task fail')
                  done(false)
              }, 1000)
          })
      }
      
  • 配置方法

    • 使用 grunt.initConfig({}) 方法進(jìn)行g(shù)runt配置參數(shù)的初始化

    • 在task的回調(diào)函數(shù)中,通過 grunt.config(key) 方法可以獲取參數(shù)配置對(duì)象中的值

      // gruntfile.js
      module.exports = grunt => {
          grunt.initConfig({
              hello: {
                  what: 'world'
              }
          })
      
          grunt.registerTask('foo', () => {
              console.log(`hello ${grunt.config('hello.what')}`)
          })
      }
      
  • 多目標(biāo)任務(wù)

    多目標(biāo)模式,可以讓任務(wù)根據(jù)配置形成多個(gè)子任務(wù)

    • 使用 grunt.registerMultiTask(<task>, <func> 來注冊(cè)多目標(biāo)任務(wù)

    • 多目標(biāo)任務(wù)需要通過 grunt.initConfig 來配置相應(yīng)的子任務(wù)

      • 以task名稱作為key,value是配置對(duì)象
      • 配置對(duì)象以子任務(wù)名稱作為key,在任務(wù)運(yùn)行時(shí)可以通過 this.target 獲取當(dāng)前執(zhí)行的子任務(wù)名稱
      • 配置對(duì)象的值為運(yùn)行時(shí)的配置數(shù)據(jù),在任務(wù)運(yùn)行時(shí)可以通過 this.data 獲取當(dāng)前執(zhí)行的子任務(wù)的配置數(shù)據(jù)
      • 當(dāng)配置對(duì)象的key為options時(shí),代表任務(wù)運(yùn)行時(shí)的選項(xiàng)配置,而不代表一個(gè)子任務(wù)
      • 子任務(wù)的配置數(shù)據(jù)中也可以包含options,此時(shí)該子任務(wù)中配置的options覆蓋任務(wù)全局的options
      // gruntfile.js
      module.exports = grunt => {
          grunt.initConfig({
              build: {
                  options: {
                      hello: 'world'
                  },
                  css: {
                      value: 'css'
                  },
                  js: {
                      options: {
                          hello: 'grunt'
                      },
                      value: 'js'
                  }
              },
              hello: {
                  what: 'world'
              }
          })
          grunt.registerMultiTask('build', function() {
              console.log(this.options())
              console.log(this.target)
              console.log(this.data)
          })
      }
      
  • 插件的使用

    • 安裝相應(yīng)的grunt插件模塊,例如 yarn add grunt-contrib-clean

    • 使用 grunt.loadNpmTasks 加載插件,并在 grunt.initConfig() 中配置相關(guān)的參數(shù)(這里grunt-contrib-clean是多目標(biāo)任務(wù))

    • 執(zhí)行插件相關(guān)的task

      // gruntfile.js
      module.exports = grunt => {
          grunt.initConfig({     
              clean: {
                  temp: 'temp/*.txt'
              }
          })
          grunt.loadNpmTasks('grunt-contrib-clean')
      }
      
  • 常用插件及總結(jié)

    • grunt-sass 用于scss轉(zhuǎn)css
      • yarn add grunt-sass sass --dev
    • grunt-babel 用于js語法轉(zhuǎn)換
      • yarn add grunt-babel @babel/core @babel/present-env --dev
    • grunt-contrib-watch 開發(fā)階段監(jiān)控文件改動(dòng),并自動(dòng)執(zhí)行配置指定的task
      • yarn add grunt-contirb-watch --dev
    • load-grunt-tasks 用于自動(dòng)加載所有g(shù)runt插件中的任務(wù)
      • yarn add load-grunt-tasks --dev

Gulp

  • gulp的基本使用

    • 新建項(xiàng)目目錄,并使用 yarn init -y 命令初始化

    • yarn add gulp 添加gulp模塊

    • 添加gulpfile.js文件

      • gulp的入口文件
      • 用于定義gulp自動(dòng)執(zhí)行的任務(wù)
      • gulp 4.0以上的版本通過exports.task的方式導(dǎo)出一個(gè)task,exports.default為默認(rèn)的task
      • gulp定義任務(wù)為異步任務(wù),需要在任務(wù)執(zhí)行的函數(shù)中調(diào)用參數(shù)傳入的方法,去標(biāo)記任務(wù)是否執(zhí)行完成
    • yarn gulp <task> 執(zhí)行g(shù)ulpfile.js中定義的task

      // gulpfile.js  gulp的入口文件
      
      exports.foo  = done => {
          console.log('gulp foo')
          done()
      }
      
      exports.default = done => {
          console.log('gulp default')
          done()
      }
      
      // gulp 4.0以前版本定義task方式
      const gulp = require('gulp')
      
      gulp.task('bar', done => {
          console.log('gulp bar')
          done()
      })
      
  • gulp的組合任務(wù)

    • gulp提供了 seriesparallel 兩種方式來組合執(zhí)行多個(gè)任務(wù)
    • series為串行執(zhí)行多個(gè)任務(wù)
    • parallel為并行執(zhí)行多個(gè)任務(wù)
    // gulpfile.js
    const { series, parallel } = require('gulp')
    
    const task1 = done => {
        setTimeout(() => {
            console.log('task 1 working')
            done()
        }, 1000);
    }
    
    const task2 = done => {
        setTimeout(() => {
            console.log('task 2 working')
            done()
        }, 1000);
    }
    
    const task3 = done => {
        setTimeout(() => {
            console.log('task 3 working')
            done()
        }, 1000);
    }
    
    exports.series = series(task1, task2, task3)
    
    exports.parallel = parallel(task1, task2, task3)
    
  • gulp的異步任務(wù)常用的幾種方式

    • 回調(diào)函數(shù)方式

      • gulp異步任務(wù)函數(shù)接收一個(gè)回調(diào)函數(shù)參數(shù),通過該回調(diào)函數(shù),可以標(biāo)記異步任務(wù)是否執(zhí)行完成,或者發(fā)生異常,當(dāng)發(fā)生異常,后續(xù)任務(wù)將不再繼續(xù)執(zhí)行

        exports.callback = done => {
            console.log('callback')
            done()
        }
        
        exports.callback_error = done => {
            console.log('callback error')
            done(new Error('callback error'))
        }
        
    • Promise

      • gulp異步任務(wù)支持使用Promise方式,標(biāo)記異步任務(wù)執(zhí)行完成,返回一個(gè)resolved狀態(tài)的Promise對(duì)象,標(biāo)記任務(wù)失敗,返回一個(gè)rejected狀態(tài)的Promise對(duì)象

        exports.promise = () => {
            console.log('promise')
            return Promise.resolve()
        }
        
        exports.promise_error = () => {
            console.log('promise error')
            return Promise.reject(new Error('promise error'))
        }
        
    • async/await

      • async/await是Promise的語法糖,使用node版本8以上時(shí),gulp也支持使用該方式處理異步任務(wù)

        const timeout = time => {
            return new Promise((resolve) => {
                setTimeout(resolve, time)
            })
        }
        
        exports.async = async () => {
            await timeout(1000)
            console.log('async timeout')
        }
        
        
    • stream

      • 通常在自動(dòng)化構(gòu)建過程中需要處理大量的文件,stream也是gulp異步任務(wù)處理中常用的方式

      • 通過返回一個(gè)stream對(duì)象,當(dāng)stream的end方法被調(diào)用時(shí),異步任務(wù)會(huì)被標(biāo)記完成

        const fs = require('fs')
        
        exports.stream = () => {
            const readStream = fs.createReadStream('package.json')
            const writeStream = fs.createWriteStream('temp.txt')
            readStream.pipe(writeStream)
            return readStream
        }
        // 等同于
        exports.stream_callback = done => {
            const readStream = fs.createReadStream('package.json')
            const writeStream = fs.createWriteStream('temp.txt')
            readStream.pipe(writeStream)
            readStream.on('end', () => {
                done()
            })   
        }
        
  • gulp構(gòu)建過程核心工作原理

    官方對(duì)于gulp的定義是the streaming build system

    即基于流的構(gòu)建系統(tǒng)

    • 構(gòu)建的流程通常是:讀取文件 -> 處理讀取內(nèi)容 -> 寫入文件

    • 對(duì)應(yīng)gulp的工作過程:讀取流(read stream) -> 轉(zhuǎn)換流(transform stream) -> 寫入流 (write stream)

      // 嘗試模擬實(shí)現(xiàn)css的壓縮
      const fs = require('fs')
      const { Transform } = require('stream')
      
      exports.transform = () => {
          const read = fs.createReadStream('style.css')
          const write = fs.createWriteStream('style.min.css')
      
          const transform = new Transform({
              transform: (chunk, encoding, callback) => {
                  const input = chunk.toString()
                  const output = input.replace(/\s+/g, '').replace(/\/\*.+?\*\//g, '') // 去除空格及注釋
                  callback(null, output)
              }
          })
      
          return read
              .pipe(transform)
              .pipe(write)
      }
      
  • gulp文件操作API

    • 使用gulp提供的src與dest來實(shí)現(xiàn)文件的讀取流與寫入流

      // 使用src批量讀取文件,使用dest批量寫入文件
      // 借助gulp-clean-css插件壓縮css
      // 借助gulp-rename插件重命名
      const { src, dest } = require('gulp')
      const cleanCss = require('gulp-clean-css')
      const rename = require('gulp-rename')
      
      exports.minify = () => {
          return src('style.css')
              .pipe(cleanCss())
              .pipe(rename({
                  extname: '.min.css'
              }))
              .pipe(dest('dist'))
      }
      
  • gulp案例

    這里通過一個(gè)工程樣例來展示,在實(shí)際開發(fā)過程中,對(duì)一個(gè)前端工程使用gulp進(jìn)行自動(dòng)化構(gòu)建可能會(huì)涉及到的各種編譯配置

    例如樣式文件sass的編譯,腳本ES6+的轉(zhuǎn)換,HTML文件的轉(zhuǎn)換等等

    工程demo見 https://github.com/zce/zce-gulp-demo.git

    • 樣式編譯

      const { src, dest } = require('gulp');
      const sass = require('gulp-sass');
      
      const style = () => {
          return src('src/assets/styles/*.scss', { base: 'src' })
              .pipe(sass({
                  outputStyle: 'expanded'
              }))
              .pipe(dest('dist/styles'))
      }
      
      module.exports = {
          style,
      }
      
    • 腳本編譯

      // gulpfile.js
      const { src, dest } = require('gulp');
      const babel = require('gulp-babel');
      
      const script = () => {
          return src('src/assets/scripts/*.js', { base: 'src' })
              .pipe(babel({
                  presets: ['@babel/preset-env']
              }))
              .pipe(dest('dist'))
      }
      
      module.exports = {
          script,
      }
      
    • 頁面模板編譯

      // gulpfile.js
      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' })
              .pipe(plugins.swig({ data, defaults: { cache: false } })) // 關(guān)閉緩存防止修改不實(shí)時(shí)生效
              .pipe(dest('temp'))
      }
      
      module.exports = {
          page,
      }
      
    • 圖片和字體文件轉(zhuǎn)換

      // gulpfile.js
      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'))
      }
      
      module.exports = {
          image,
          font,
      }
      
    • 其他文件及文件清除

      const del = require('del');
      
      const extra = () => {
          return src('public/**', { base: 'public' })       
              .pipe(dest('dist'))
      }
      
      const clean = () => {
          return del(['dist'])
      }
      
    • 自動(dòng)加載插件

      const loadPlugins = require('gulp-load-plugins');
      const plugins = loadPlugins();
      // 使用gulp-load-plugins插件自動(dòng)加載所有已安裝的以gulp-開頭的插件,并使用plugins.xxxx訪問相應(yīng)的插件
      // 例如使用plugins.sass訪問gulp-sass插件
      
    • 開發(fā)服務(wù)器

      const browserSync = require('browser-sync');
      
      const bs = browserSync.create();
      
      const serve = () => {
          bs.init({
              notify: false,
              files: 'dist/**',
              server: {
                  baseDir: 'dist',
                  routes: {
                      '/node_modules': './node_modules'
                  }
              }
          })
      }
      
    • 監(jiān)視變化以及構(gòu)建優(yōu)化

      • 使用gulp提供的watch API來監(jiān)控指定文件,并在文件變化時(shí),執(zhí)行相應(yīng)的task
      • watch接收兩個(gè)參數(shù),第一個(gè)參數(shù)是文件通配符字符串或者通配符字符串?dāng)?shù)組,表示要監(jiān)視的文件,第二個(gè)參數(shù)是文件變化時(shí)要執(zhí)行的task函數(shù)
      • 在開發(fā)階段,對(duì)于圖片及字體的壓縮以及靜態(tài)資源的拷貝意義不大,同時(shí)會(huì)增加構(gòu)建任務(wù)的開銷,將這些文件保留在源文件目錄(需指定baseDir)并直接通過watch對(duì)這些目錄進(jìn)行監(jiān)視,在文件發(fā)生變化時(shí),執(zhí)行bs.reload刷新頁面
      // gulpfile.js
      const { src, dest, series, parallel, watch } = require('gulp');
      
      const bs = browserSync.create();
      
      const serve = () => {
          watch('src/assets/styles/*.scss', style)
          watch('src/assets/scripts/*.js', script)
      watch('src/**/*.html', page)
          // watch('src/assets/images/**', image) 開發(fā)階段意義不大
          // watch('src/assets/fonts/**', font) 開發(fā)階段意義不大
          // watch('public/**', extra) 開發(fā)階段意義不大
          watch([
              'src/assets/images/**',
              'src/assets/fonts/**',
              'public/**',
          ], bs.reload)
      
          bs.init({
              notify: false,
              files: 'dist/**',
              server: {
                  baseDir: ['dist', 'src', 'public'],
                  routes: {
                      '/node_modules': './node_modules'
                  }
              }
          })
      }
      
    • useref文件引用處理

      • 依賴gulp-useref插件,可以將html中依賴的js、css根據(jù)注釋提取并生成到指定的文件中,并替換依賴的資源文件路徑為新生成的文件路徑
      const useref = () => {
          return src('dist/*.html', { base: 'dist' })
              .pipe(plugins.useref({
                  searchPath: ['dist', '.']
              }))
              .pipe(dest('dist'))
      }
      
    • 文件壓縮

      • 使用相關(guān)的gulp插件對(duì)相應(yīng)類型的文件進(jìn)行壓縮處理
        • gulp-uglify 壓縮js文件
        • gulp-clean-css 壓縮css文件
        • gulp-htmlmin 壓縮html文件
      • 為避免文件讀寫沖突,可以將壓縮后的代碼放入另外的文件夾,例如release
      const useref = () => {
          return src('dist/*.html', { base: 'dist' })
              .pipe(plugins.useref({
                  searchPath: ['dist', '.']
              }))
              .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('release'))
      }
      
    • 重新規(guī)劃構(gòu)建過程

      • 對(duì)上述構(gòu)建任務(wù)進(jìn)行重新整理
      • style、script、page可以通過parallel組合成并行處理任務(wù)compile const compile = parallel(style, script, page),生成的文件由于需要后續(xù)進(jìn)行useref壓縮處理,構(gòu)建過程中臨時(shí)存放在temp目錄,最終壓縮后放入dist目錄
      • useref需要先進(jìn)行compile生成臨時(shí)文件,可以使用series組合成串行處理任務(wù),最終組合成構(gòu)建任務(wù)build const build = series(clean, parallel(series(compile, useref), image, font, extra))
      • 去除不必要exports的任務(wù)
      • 將對(duì)應(yīng)的構(gòu)建任務(wù)加入package.json的script中
      • 完整的gulpfile.js如下
       // gulpfile.js
      const { src, dest, series, parallel, watch } = require('gulp');
      const loadPlugins = require('gulp-load-plugins');
      const browserSync = require('browser-sync');
      const del = require('del');
      
      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 style = () => {
          return src('src/assets/styles/*.scss', { base: 'src' })
              .pipe(plugins.sass({
                  outputStyle: 'expanded'
              }))
              .pipe(dest('temp'))
      }
      
      const script = () => {
          return src('src/assets/scripts/*.js', { base: 'src' })
              .pipe(plugins.babel({
                  presets: ['@babel/preset-env']
              }))
              .pipe(dest('temp'))
      }
      
      const page = () => {
          return src('src/**/*.html', { base: 'src' })
              .pipe(plugins.swig({ data }))
              .pipe(dest('temp'))
      }
      
      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 clean = () => {
          return del(['dist', 'temp'])
      }
      
      const compile = parallel(style, script, page)
      
      const serve = () => {
          watch('src/assets/styles/*.scss', style)
          watch('src/assets/scripts/*.js', script)
          watch('src/**/*.html', page)
          // watch('src/assets/images/**', image) 開發(fā)階段意義不大
          // watch('src/assets/fonts/**', font) 開發(fā)階段意義不大
          // watch('public/**', extra) 開發(fā)階段意義不大
          watch([
              'src/assets/images/**',
              'src/assets/fonts/**',
              'public/**',
          ], bs.reload)
      
      
          bs.init({
              notify: false,
              files: 'temp/**',
              server: {
                  baseDir: ['temp', 'src', 'public'],
                  routes: {
                      '/node_modules': './node_modules'
                  }
              }
          })
      }
      
      const useref = () => {
          return src('temp/*.html', { base: 'temp' })
              .pipe(plugins.useref({
                  searchPath: ['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('dist'))
      }
      
      const build = series(clean, parallel(series(compile, useref), image, font, extra))
      
      const dev = series(compile, serve)
      
      module.exports = {
          clean,
          build,
          dev,
      }
      
  • 封裝工作流

    多個(gè)項(xiàng)目中復(fù)用gulpfile中的task

    • 準(zhǔn)備

      • 創(chuàng)建項(xiàng)目托管repository(github/gitee等)
      • 創(chuàng)建項(xiàng)目目錄xxx,并初始化 yarn init -y
      • 創(chuàng)建目錄結(jié)構(gòu)及文件lib/index.js
    • 提取gulpfile

      • 將原gulpfile.js中的內(nèi)容復(fù)制到lib/index.js文件中
      • 將原package.json中devDependencies中的依賴添加到當(dāng)前package.json的dependencies中
      • yarn安裝相應(yīng)的依賴
      • yarn link 建立軟連接
      • 進(jìn)入待構(gòu)建項(xiàng)目目錄,執(zhí)行yarn link xxx,執(zhí)行相應(yīng)的命令yarn xxx,測(cè)試當(dāng)前的封裝
    • 解決模塊中的問題

      • 將原gulpfile.js中使用到的data數(shù)據(jù),改為讀取配置文件xxx.config.js
      • 對(duì)babel使用的插件模塊,使用require引入
    • 抽象路徑配置

      • 將原gulpfile.js中使用到的項(xiàng)目路徑,通過config進(jìn)行配置,并提供默認(rèn)值,同時(shí)替換所有使用位置為config讀取

        // lib/index.js
        const { src, dest, series, parallel, watch } = require('gulp');
        const loadPlugins = require('gulp-load-plugins');
        const browserSync = require('browser-sync');
        const del = require('del');
        
        const plugins = loadPlugins();
        
        const bs = browserSync.create();
        
        const cwd = process.cwd()
        let config = {
            // default config
            build: {
                src: 'src',
                temp: 'temp',
                dist: 'dist',
                public: 'public',
                paths: {
                    styles: 'assets/styles/*.scss',
                    scripts: 'assets/scripts/*.js',
                    pages: '*.html',
                    images: 'assets/images/**',
                    fonts: 'assets/fonts/**',
                }
            }
        }
        
        try {
            const loaded = require(`${cwd}/pages.config.js`)
            config = { ...config, ...loaded }
        } catch(e) {}
         
        
        const style = () => {
            return src(config.build.paths.styles, { base: config.build.src, cwd: config.build.src })
                .pipe(plugins.sass({
                    outputStyle: 'expanded'
                }))
                .pipe(dest(config.build.temp))
        }
        
        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))
        }
        
        const page = () => {
            return src(config.build.paths.pages, { base: config.build.src, cwd: config.build.src })
                .pipe(plugins.swig({ data: config.data }))
                .pipe(dest(config.build.temp))
        }
        
        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))
        }
        
        const extra = () => {
            return src('**', { base: config.build.public, cwd: config.build.public })        
                .pipe(dest(config.build.dist))
        }
        
        const clean = () => {
            return del([config.build.dist, config.build.temp])
        }
        
        const compile = parallel(style, script, page)
        
        const serve = () => {
            watch(config.build.paths.styles, { cwd: config.build.src}, style)
            watch(config.build.paths.scripts, { cwd: config.build.src}, script)
            watch(config.build.paths.pages, { cwd: config.build.src}, page)
            // watch('src/assets/images/**', image) 開發(fā)階段意義不大
            // watch('src/assets/fonts/**', font) 開發(fā)階段意義不大
            // watch('public/**', extra) 開發(fā)階段意義不大
            watch([
                config.build.paths.images,
                config.build.paths.fonts,        
            ], { cwd: config.build.src }, bs.reload)
        
            watch([
                '**',        
            ], { cwd: config.build.public }, bs.reload)
        
            bs.init({
                notify: false,
                files: `${config.build.temp}/**`,
                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.paths.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 build = series(clean, parallel(series(compile, useref), image, font, extra))
        
        const dev = series(compile, serve)
        
        module.exports = {
            clean,
            build,
            dev,
        }
        
    • 包裝Gulp CLI

      • 項(xiàng)目目錄下新建bin/xxx.js

      • package.js中增加bin選項(xiàng)配置對(duì)應(yīng)的文件

      • bin/xxx.js中,讀取process.argv命名行參數(shù)數(shù)組,并添加gulp執(zhí)行相關(guān)參數(shù)

      • require('gulp/bin/gulp') 執(zhí)行g(shù)ulp命令

        #!/usr/bin/env node
        
        process.argv = process.argv.concat(['--cwd', process.cwd(), '--gulpfile', require.resolve('..')])
        
        require('gulp/bin/gulp')
        
    • 發(fā)布并使用模塊

      • 執(zhí)行yarn publish --registry=https://registry.yarnpkg.com發(fā)布封裝的工作流模塊到npm
      • 在待使用的項(xiàng)目中,執(zhí)行yarn add xxx --dev添加相應(yīng)的依賴
      • xxx build 進(jìn)行使用

FIS

由百度前端團(tuán)隊(duì)推出的自動(dòng)化構(gòu)建工具,內(nèi)置了很多常用的任務(wù)以及devServer,不需要開者自己去配置

  • 基本使用
    • yarn add fis3 --dev 安裝fis3模塊
    • fis3 release 編譯,使用-d參數(shù)可以指定輸出目錄 fis3 release -d output
    • 添加配置文件fis-config.js,通過配置文件可以對(duì)fis3編譯進(jìn)行配置
  • 詳細(xì)用法
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容