筆記六:開發(fā)腳手架與自動化構建工作流封裝

工程化概述

前端工程化事指遵循一定的標準和規(guī)范,通過工具去提高效率辕棚、降低成本和質(zhì)量保證的一種手段

前端開發(fā)中遇到的問題

  • 想要使用ES6+新特性,但是存在兼容問題
  • 想要使用Less/Saa/PostCss增強Css的編程性邓厕,但是運行環(huán)境不能直接支持
  • 想要使用模塊化的方式提高項目的可維護性逝嚎,但運行環(huán)境不能直接支持
  • 部署上線前需要手動壓縮代碼及資源文件,部署過程需要手動上傳代碼到服務器
  • 多人協(xié)同開發(fā)详恼,無法硬性統(tǒng)一大家的代碼峰哥补君,從倉庫中pull回來的代碼質(zhì)量無法保證

主要解決的問題

  • 創(chuàng)痛語言或語法的弊端
  • 無法使用模塊化/組件化
  • 重復的機械式工作
  • 代碼風格統(tǒng)一、質(zhì)量保證
  • 依賴后端服務接口支持
  • 整體依賴后端項目

工程化表現(xiàn)

  • 創(chuàng)建項目
    • 創(chuàng)建項目結構
    • 創(chuàng)建特定類型文件
  • 編碼
    • 格式化代碼
    • 校驗代碼風格
    • 編譯/構建/打包
  • 預覽/測試
    • Web Server / Mock
    • Lice Reloading / HMR
    • Source Map
  • 提交
    • Git Hooks
    • Lint-staged
    • 持續(xù)集成
  • 部署
    • CI / CD
    • 自動發(fā)布

工程化不等于某個具體工具

工具并不是工程化的核心昧互,工程化的核心事對項目的整體規(guī)劃或架構挽铁,工具只是落地和實現(xiàn)工程化的一個手段
**常見的一些成熟的工程化集成

  • create-react-app
  • vue-cli
  • angular-cli
  • gatsby-cli
    以上幾個是某個項目官方提供的集成化方案

工程化與Node.js

工程化工具都是Node.js開發(fā)的

腳手架工具

腳手架的本質(zhì)作用就是創(chuàng)建項目基礎結構、提供項目規(guī)范和約定

腳手架工具的作用

因為在前端工程中硅堆,可以存在有:

  • 相同的組織結構
  • 相同的開發(fā)范式
  • 相同的模塊依賴
  • 相同的工具配置
  • 相同的基礎代碼
    腳手架就是解決上面問題的工具屿储,通過創(chuàng)建項目骨架自動的執(zhí)行工作。IDE(如Android studio)創(chuàng)建項目的過程就是一個腳手架的工作流程
    由于前端技術選型比較多樣渐逃,有沒有一個統(tǒng)一的標準,所以前端腳手架不會集成在某一個具體的IDE民褂,一般都是以一個獨立的工具存在茄菊,相對回復雜一些

常用的腳手架工具

  • 第一類腳手架是更具信息創(chuàng)建對應的項目基礎結構,適用于自身所服務的框架的那個項目
    • create-react-app
    • vue-cli
    • angular-cli
  • 第二類是向Yeoman為代表的通用性腳手架工具赊堪,回根據(jù)模板生成統(tǒng)一的項目結構面殖,這種腳手架工具很靈活,很容易擴展
  • 第三類以Plop為代表的腳手架工具哭廉,是在項目開發(fā)過程中脊僚,創(chuàng)建一些特定類型的組件,例如創(chuàng)建一個組件/模塊所需要的文件,這些文件一般都是由特定結構組成的辽幌,由相同的結構

通用腳手架工具剖析

1.Yeoman + Generator

Yeoman是最老牌增淹、最強大、最通用的腳手架工具乌企,是創(chuàng)建現(xiàn)代化應用的腳手架工具虑润,不同于vue-cli,Yeoman更像是腳手架運行平臺加酵,我們可以通過Yeoman搭配不同的Generator去創(chuàng)建任何類型的項目拳喻,我們可以創(chuàng)建我們自己的Generator,從而去創(chuàng)建我們自己的前端腳手架猪腕。缺點是冗澈,在框架開發(fā)的項目中,Yeoman過于通用不夠?qū)Wⅰ?/p>

如果使用Yeoman

  • 在電腦全局安裝Yeoman:yarn global add yo
  • Yeoman要搭配想用的Generator創(chuàng)建任務陋葡,所以要安裝Generator亚亲。使用npm安裝命令:npm install global generator-node,使用yarn安裝命令:yarn add global generator-node
  • 創(chuàng)建一個空文件夾:mkdir my-module脖岛,然后進入文件夾:cd my-module
  • 通過Yeoman的yo命令安裝剛才的生成器:yo node
  • 交互模式填寫一些項目信息朵栖,會生成項目基礎結構,并還說呢工程一些項目文件柴梆,然后自動運行npm install安裝一些項目依賴
2.SubGenerator

有時候我們可能不需要創(chuàng)建一個完成的項目結構陨溅,而是在已有項目的基礎上,創(chuàng)建一些項目文件绍在,如README.md门扇,或者是創(chuàng)建一些特定類型的文件,如ESLint偿渡、Babel配置文件

  • 運行SubGenerator的方式就是在原有Generator基礎上加上SubGenerator的名字臼寄,如:yo node:cli
  • 在使用gSubGenerator前,要先去查看一下
    Generator之下有哪些SubGenerator
3.Plop

Plop是一個小而美的腳手架工具溜宽,通常用于創(chuàng)建項目中特定類型文件的小工具吉拳,一般是把Plop集成到項目中,用來自動化創(chuàng)建同類型的項目文件适揉。

如何使用Polp創(chuàng)建文件:

  • 將plop模塊作為項目開發(fā)依賴安裝
  • 在項目根目錄下創(chuàng)建一個plopfile.js文件
  • 在plopfile留攒。js文件中定義腳手架任務
  • 編寫用于生成特定類型文件的模板
  • 通過Plop提供的cli運行腳手架任務
4.腳手架工作原理

腳手架的工作原理就是在啟動腳手架之后,回自動地區(qū)詢問一些預設問題嫉嘀,通過回答的結構并結合一些模板文件炼邀,生成項目的結構,使用NodeJS開發(fā)一個小型的腳手架工具

  • 用yarn init初始化一個空文件夾:sample-scaffolding
  • 在package.json中添加bin屬性指定腳手架的命令入口文件為cli.js
{
  "name": "sample-scaffolding",
  "version": "0.1.0",
  "main": "index.js",
  "bin": "cli.js",
  "author": "zce <w@zce.me> (https://zce.me)",
  "license": "MIT",
  "dependencies": {
    "ejs": "^2.6.2",
    "inquirer": "^7.0.0"
  }
}
  • 編寫cli.js
#!/usr/bin/env node

// Node CLI 應用入口文件必須要有這樣的文件頭
// 如果是 Linux 或者 macOS 系統(tǒng)下還需要修改此文件的讀寫權限為 755
// 具體就是通過 chmod 755 cli.js 實現(xiàn)修改

// 腳手架的工作過程:
// 1. 通過命令行交互詢問用戶問題
// 2. 根據(jù)用戶回答的結果生成文件

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)
  // 根據(jù)用戶回答的結果生成文件

  // 模板目錄
  const tmplDir = path.join(__dirname, 'templates')
  // 目標目錄
  const destDir = process.cwd()

  // 將模板下的文件全部轉(zhuǎn)換到目標目錄
  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)
      })
    })
  })
})
  • 將該cli程序link到全局:yarn link
  • 然后在其他文件夾中執(zhí)行:sample-scaffolding命令剪侮,就可以根據(jù)模板自動化創(chuàng)建文件了拭宁。
5.自定義Generator開發(fā)腳手架

注意:Yeoman的生成器名稱必須是generator-<name>,安裝生成器的時候,就執(zhí)行yo <name>

創(chuàng)建Generator生成器的步驟

  • mkdir generator-sample
  • cd generator-sample
  • yarn init
  • yarn add yeoman-generator
  • 創(chuàng)建文件:generators/app/index.jsx
// 此文件作為 Generator 的核心入口
// 需要導出一個繼承自 Yeoman Generator 的類型
// Yeoman Generator 在工作時會自動調(diào)用我們在此類型中定義的一些生命周期方法
// 我們在這些方法中可以通過調(diào)用父類提供的一些工具方法實現(xiàn)一些功能,例如文件寫入

const Generator = require('yeoman-generator')

module.exports = class extends Generator {
  prompting () {
    // Yeoman 在詢問用戶環(huán)節(jié)會自動調(diào)用此方法
    // 在此方法中可以調(diào)用父類的 prompt() 方法發(fā)出對用戶的命令行詢問
    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 () {
    // Yeoman 自動在生成文件階段調(diào)用此方法

    // // 我們這里嘗試往項目目錄中寫入文件
    // this.fs.write(
    //   this.destinationPath('temp.txt'),
    //   Math.random().toString()
    // )

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

    // // 通過模板方式寫入文件到目標目錄

    // // 模板文件路徑
    // const tmpl = this.templatePath('foo.txt')
    // // 輸出目標路徑
    // const output = this.destinationPath('foo.txt')
    // // 模板數(shù)據(jù)上下文
    // const context = { title: 'Hello zce~', success: false }

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

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

    // 模板文件路徑
    const tmpl = this.templatePath('bar.html')
    // 輸出目標路徑
    const output = this.destinationPath('bar.html')
    // 模板數(shù)據(jù)上下文
    const context = this.answers

    this.fs.copyTpl(tmpl, output, context)
  }
}
  • templates/foo.txt作為模板文件
這是一個模板文件
內(nèi)部可以使用 EJS 模板標記輸出數(shù)據(jù)
例如:<%= title %>

其他的 EJS 語法也支持

<% if (success) { %>
哈哈哈
<% }%>
  • 執(zhí)行yarn link杰标,此時這個模板就回作為全局模塊被link到全局兵怯,別的項目就可以直接使用它
  • 創(chuàng)建一個別的文件夾my-proj,在這個文件夾中執(zhí)行:yo sample
  • 發(fā)布到npmjs網(wǎng)站上:yarn publish --registry=https://registry.yarnpkg.com
5.Plop

yarn add plop
plopfile.js

// Plop 入口文件,需要導出一個函數(shù)
// 此函數(shù)接收一個 plop 對象在旱,用于創(chuàng)建生成器任務

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'
      }
    ]
  })
}

編寫模板:
component.hbs:

import React from 'react';

export default () => (
  <div className="{{name}}">
    <h1>{{name}} Component</h1>
  </div>
)

Component.css.hbs:

import React from 'react';
import ReactDOM from 'react-dom';
import {{name}} from './{{name}}';

it('renders without crashing', () => {
  const div = document.createElement('div');
  ReactDOM.render(<{{name}} />, div);
  ReactDOM.unmountComponentAtNode(div);
});

執(zhí)行命令:yarn plop component

自動化構建

源代碼自動化構建成生產(chǎn)代碼摇零,也成為自動化構建工作流
使用提高效率的語法、規(guī)范和標準桶蝎,如:EXMAScript Next驻仅、Sass、模板引擎登渣,這些用法大都不被瀏覽器直接支持噪服,自動化工具就是解決這些問題的,構建轉(zhuǎn)換那些不被支持的特性

1.NPM Scripts

在package.json中增加一個scripts對象胜茧,如:

{
  "scripts": {
    "build": "sass scss/main.scss css/style.css"
  }
}

scripts可以自動發(fā)現(xiàn)node_modules里面的命令粘优,所以不需要寫完整的路徑,直接寫命令的名稱就可以呻顽。然后可以通過npm或yarn運行scripts下面的命令名稱雹顺,npm用run啟動:npm run build ,或使用yarn啟動:yarn build
**NPM Scripts是實現(xiàn)自動化構建工作流的最簡單方式

{
  "scripts": {
    "build": "sass scss/main.scss css/style.css",
    "preserve": "yarn build",
    "serve": "browser-sync ."
  }
}

preserve是一個鉤子廊遍,保證在執(zhí)行serve之前嬉愧,回先執(zhí)行build,使樣式先處理喉前,然后再執(zhí)行serve
通過--watch可以監(jiān)聽sass文件的變化自動編譯没酣,但是此時sass命令在工作時,命令行會阻塞卵迂,去等待文件的變化裕便,導致了后面的serve無法去工作,此時就需要同時執(zhí)行多個任務见咒,要安裝npm-run-all這個模塊

{
  "scripts": {
    "build": "sass scss/main.scss css/style.css --watch",
    "serve": "browser-sync .",
    "start": "run-p build serve"
  }
}

運行npm run start命令偿衰,build和serve就回被同時執(zhí)行

2.Grunt

Grunt時最早的前端構建系統(tǒng),它的插件生態(tài)非常完善改览,它的插件可以幫你完成任何你想做的事情哎垦。由于Grunt工作過程時基于臨時文件去實現(xiàn)的,所以會比較慢
如何使用Grunt:

  • 安裝grunt: yarn add grunt, 編寫gruntfile.js文件恃疯,下面舉例grunt任務的集中用法:
// Grunt的入口文件
// 用于定義一些需要Grunt自動執(zhí)行的任務
// 需要導出一個函數(shù)
// 此函數(shù)接受一個grunt的形參,內(nèi)部提供一些創(chuàng)建任務時可以用到的API

module.exports = grunt => {
  grunt.registerTask('foo', () => {// 第一個參數(shù)是任務名字墨闲,第二個參數(shù)接受一個回調(diào)函數(shù)今妄,是指定任務的執(zhí)行內(nèi)容,執(zhí)行命令是yarn grunt foo
    console.log('hello grunt ~')
  })

  grunt.registerTask('bar', '任務描述', () => { // 如果第二個參數(shù)是字符串,則是任務描述盾鳞,執(zhí)行命令是yarn grunt bar
    console.log('other task~')
  })

  grunt.registerTask('default', () => { // 如果任務名稱是'default'犬性,則為默認任務,grunt在運行時不需要執(zhí)行任務名稱腾仅,自動執(zhí)行默認任務乒裆,執(zhí)行命令是yarn grunt
    console.log('default task')
  })

  grunt.registerTask('default', ['foo', 'bad', 'bar']) // 一般用default映射其他任務,第二個參數(shù)傳入一個數(shù)組推励,數(shù)組中指定任務的名字鹤耍,grunt執(zhí)行默認任務,則會依次執(zhí)行數(shù)組中的任務验辞,執(zhí)行命令是yarn grunt

  // grunt.registerTask('async-task', () => {
  //   setTimeout(() => {
  //     console.log('async task working')
  //   }, 1000);
  // })

  // 異步任務稿黄,done()表示結束
  grunt.registerTask('async-task', function () { // grunt代碼默認支持同步模式,如果需要異步操作跌造,則需要通過this.async()得到一個回調(diào)函數(shù)杆怕,在你的異步操作完成過后,去調(diào)用這個回調(diào)函數(shù)壳贪,標記這個任務已經(jīng)被完成陵珍。知道done()被執(zhí)行,grunt才會結束這個任務的執(zhí)行违施。執(zhí)行命令是yarn grunt async-task
    const done = this.async()
    setTimeout(() => {
      console.log('async task working..')
      done()
    }, 1000);
  })

  // 失敗任務
  grunt.registerTask('bad', () => { // 通過return false標志這個任務執(zhí)行失敗,執(zhí)行命令是yarn grunt bad互纯。如果是在任務列表中,這個任務的失敗會導致后序所有任務不再被執(zhí)行,執(zhí)行命令是yarn grunt醉拓∥敖悖可以通過--force參數(shù)強制執(zhí)行所有的任務,,執(zhí)行命令是yarn grunt default --force
    console.log('bad working...')
    return false
  })

  // 異步失敗任務亿卤,done(false)表示任務失敗,執(zhí)行命令是yarn grunt bad-async-task
  grunt.registerTask('bad-async-task', function () {
    const done = this.async()
    setTimeout(() => {
      console.log('bad async task working..')
      done(false)
    }, 1000);
  })
}
  • grunt配置選項
module.exports = grunt => {

  grunt.initConfig({
    // 對象的屬性名一般與任務名保持一致愤兵。
    // foo: 'bar'
    foo: {
      bar: 123
    }
  })

  grunt.registerTask('foo', () => {
    // console.log(grunt.config('foo')) // bar
    console.log(grunt.config('foo.bar')) // 123.grunt的config支持通過foo.bar的形式獲取屬性值,也可以通過獲取foo對象排吴,然后取屬性
  })
}
  • 多目標任務(相當于子任務)
module.exports = grunt => {

  grunt.initConfig({
    // 與任務名稱同名
    build: {
      options: { // 是配置選項秆乳,不會作為任務
        foo: 'bar'
      },
      // 每一個對象屬性都是一個任務
      css: {
        options: { // 會覆蓋上層的options
          foo: 'baz'
        }
      },
      // 每一個對象屬性都是一個任務
      js: '2'
    }
  })
  
  // 多目標任務,可以讓任務根據(jù)配置形成多個子任務钻哩,registerMultiTask方法屹堰,第一個參數(shù)是任務名,第二個參數(shù)是任務的回調(diào)函數(shù)
  grunt.registerMultiTask('build', function () {
    console.log(this.options())
    console.log(`build task: ${this.target}, data: ${this.data}`)
  })
}

執(zhí)行命令:yarn grunt build,輸出結果

Running "build:css" (build) task
{ foo: 'baz' }
build task: css, data: [object Object]

Running "build:js" (build) task
{ foo: 'bar' }
build task: js, data: 2
  • grunt插件使用
    插件機制時grunt的和興街氢,因為很多構建任務都是通用的扯键,社區(qū)當中也就出現(xiàn)了很多通用的插件,這些插件中很多通用的任務珊肃,一般情況下我們的構建過程都是由通用的構建任務組成的荣刑。先去npm中安裝需要的插件馅笙,再去gruntfile中使用grunt.loadNpmTasks方法載入這個插件,最后根據(jù)插件的文檔完成相關的配置選項厉亏。
    例如使用clean插件董习,安裝yarn add grunt-contrib-clean,用來清除臨時文件
module.exports = grunt => {
// 多目標任務需要通過initConfig配置目標
  grunt.initConfig({
    clean: {
      temp: 'temp/**' // ** 表示temp下的子目錄以及子目錄下的文件
    }
  })

  grunt.loadNpmTasks('grunt-contrib-clean')
}

執(zhí)行:yarn grunt clean,就回刪除temp文件

  • Grunt常用插件總結:
    • grunt-sass
    • grunt-babel
    • grunt-watch
const sass = require('sass')
const loadGruntTasks = require('load-grunt-tasks')
module.exports = grunt => {
  grunt.initConfig({
    sass: {
      options: {
        sourceMap: true,
        implementation: sass, // implementation指定在grunt-sass中使用哪個模塊對sass進行編譯,我們使用npm中的sass
      },
      main: {
        files: {
          'dist/css/main.css': 'src/scss/main.scss'
        }
      }
    },
    babel: {
      options: {
        presets: ['@babel/preset-env'],
        sourceMap: true
      },
      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'])
3.Gulp

Gulp時目前世界上最流行的前端構建系統(tǒng)爱只,其核心特點就是高效皿淋、易用。她很好的解決了Grunt中讀寫磁盤慢的問題恬试,Gulp時基于內(nèi)存操作的窝趣,Gulp支持同時執(zhí)行多個任務,效率自然大大提高忘渔,而且它的使用方式相對于Grunt更加易懂高帖,而且Gulp的生態(tài)也非常完善,所以后來居上畦粮,更受歡迎

  • gulp的使用
    安裝gulp:yarn add gulp散址,然后編寫gulpfile.js,通過到處函數(shù)成員的方式定義gulp任務
// gulp的入口文件
exports.foo = done => {
  console.log('foo task working...')
  done() // 使用done()標識任務完成
}

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

執(zhí)行命令:yarn gulp foo執(zhí)行foo任務宣赔,或者yarn gulo執(zhí)行默認任務default
gulp4.0之前的任務寫法:

const gulp = require('gulp')

gulp.task('bar', done => {
  console.log('bar working...')
  done()
})

執(zhí)行命令yarn gulp bar可以運行bar任務预麸,gulp4.0之后也保留了這個API,但是不推薦使用了

  • gulp 創(chuàng)建組合任務:series(串行)儒将、parallel(并行)
const {series, parallel} = require('gulp')

// gulp的入口文件
exports.foo = done => {
  console.log('foo task working...')

  done() // 標識任務完成
}

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

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);
}

// series 串行執(zhí)行
// exports.bar = series(task1, task2, task3)

// parallel 并行執(zhí)行
exports.bar = parallel(task1, task2, task3)
  • gulp異步任務:
const fs = require('fs')

exports.callback = done => {
  console.log('callback task...')
  done() // 通過使用done()標志異步任務執(zhí)行結束
}

exports.callback_error = done => {
  console.log('callback task...')
  done(new Error('task failed!')) // done函數(shù)也是錯誤優(yōu)先回調(diào)函數(shù)吏祸。如果這個任務失敗了,后序任務也不會工作了
}

exports.promise = () => {
  console.log('promise task...')
  return Promise.resolve() // resolve執(zhí)行的時候钩蚊,表示異步任務執(zhí)行結束了贡翘。resolve不需要參數(shù),因為gulp會忽略它的參數(shù)
}

exports.promise_error = () => {
  console.log('promise task...')
  return Promise.reject(new Error('task failed')) // reject標志這是一個失敗的任務砰逻,后序的任務也會不再執(zhí)行
}

const timeout = time => {
  return new Promise(resolve => {
    setTimeout(resolve, time);
  })
}
exports.async = async() => {
  await timeout(1000) // 在node8以上可以使用async和await鸣驱,await的就是一個Promise對象
  console.log('async task...')
}

exports.stream = (done) => { // 最常用的就是基于stream的異步任務
  const readStream = fs.createReadStream('package.json')
  const writeSteam = fs.createWriteStream('temp.txt')
  readStream.pipe(writeSteam)
  return readStream  // 相當于下面的寫法
  // readStream.on('end', () => {
  //    done()
  // })
}
  • gulp構建過程,例子:壓縮CSS
const fs = require('fs')
const {Transform} = require('stream')

exports.default = () => {
  const read = fs.createReadStream('normalize.css')
  const write = fs.createWriteStream('normalize.min.css')
  // 文件轉(zhuǎn)化流
  const transform = new Transform({
    transform: (chunk, encoding, callback) => {
      // 核心轉(zhuǎn)化過程
      // chunk => 讀取流中讀取的內(nèi)容(Buffer )
      const input = chunk.toString()
      // 轉(zhuǎn)化空白符和注釋
      const output = input.replace(/\s+/g, '').replace(/\/\*.+?\*\//g, '')
      callback(null, output)
    }
  })

  read
  .pipe(transform) // 先轉(zhuǎn)化
  .pipe(write)

  return read
}
  • gulp文件api
const {src, dest} = require('gulp')
const cleanCss = require('gulp-clean-css')
const rename = require('gulp-rename')

exports.default = () => {
  return src('src/*.css')
  .pipe(cleanCss())
  .pipe(rename({ extname: '.min.css' }))
  .pipe(dest('dist'))
}
  • gulp構建
// 實現(xiàn)這個項目的構建任務
const {src, dest, parallel, series, watch} = require('gulp')

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

const bs = browserSync.create()

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

const {sass, babel, swig, imagemin} = plugins

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(sass({ outputStyle: 'expanded' }))
  .pipe(dest('temp'))
  .pipe(bs.reload({stream: true}))
}

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

const page = () => {
  return src('src/**/*.html', {base: 'src'})
  .pipe(swig(data))
  .pipe(dest('temp'))
  .pipe(bs.reload({stream: true}))
}

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'))
}

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/**',
    'src/assets/fonts/**',
    'public/**'
  ], bs.reload)

  bs.init({
    notify: false,
    port: 2080,
    open: 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 compile = parallel(style, script, page, image, font)
const compile = parallel(style, script, page)

// 上線之前執(zhí)行的任務
const build = series(
  clean,
  parallel(
    series(compile, useref),
    image,
    font,
    extra
  )
)

// 開發(fā)階段
const develop = series(compile, serve)

module.exports = {
  clean,
  compile,
  build,
  develop,
}
4.FIS

FIS是百度的前端團隊推出的構建系統(tǒng)蝠咆,F(xiàn)I是相對于前兩種微內(nèi)核的特點踊东,它更像是一種捆綁套餐,它把我們需求都盡可能的集成在內(nèi)部刚操,例如資源加載闸翅、模塊開發(fā)、代碼部署菊霜、甚至是性能優(yōu)化坚冀。正是因為FIS的大而全,所以在國內(nèi)流行鉴逞,更適合初學者使用

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末遗菠,一起剝皮案震驚了整個濱河市联喘,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌辙纬,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件叭喜,死亡現(xiàn)場離奇詭異贺拣,居然都是意外死亡,警方通過查閱死者的電腦和手機捂蕴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進店門譬涡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人啥辨,你說我怎么就攤上這事涡匀。” “怎么了溉知?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵陨瘩,是天一觀的道長。 經(jīng)常有香客問我级乍,道長舌劳,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任玫荣,我火速辦了婚禮甚淡,結果婚禮上,老公的妹妹穿的比我還像新娘捅厂。我一直安慰自己贯卦,他們只是感情好,可當我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布焙贷。 她就那樣靜靜地躺著撵割,像睡著了一般。 火紅的嫁衣襯著肌膚如雪盈厘。 梳的紋絲不亂的頭發(fā)上睁枕,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天,我揣著相機與錄音沸手,去河邊找鬼外遇。 笑死,一個胖子當著我的面吹牛契吉,可吹牛的內(nèi)容都是我干的跳仿。 我是一名探鬼主播,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼捐晶,長吁一口氣:“原來是場噩夢啊……” “哼菲语!你這毒婦竟也來了妄辩?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤山上,失蹤者是張志新(化名)和其女友劉穎眼耀,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體佩憾,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡哮伟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了妄帘。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片楞黄。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖抡驼,靈堂內(nèi)的尸體忽然破棺而出鬼廓,到底是詐尸還是另有隱情,我是刑警寧澤致盟,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布碎税,位于F島的核電站,受9級特大地震影響勾邦,放射性物質(zhì)發(fā)生泄漏蚣录。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一眷篇、第九天 我趴在偏房一處隱蔽的房頂上張望萎河。 院中可真熱鬧,春花似錦蕉饼、人聲如沸虐杯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽擎椰。三九已至,卻和暖如春创肥,著一層夾襖步出監(jiān)牢的瞬間达舒,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工叹侄, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留巩搏,地道東北人。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓趾代,卻偏偏與公主長得像贯底,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子撒强,可洞房花燭夜當晚...
    茶點故事閱讀 44,779評論 2 354