自動化構建
文章說明:本文章為拉鉤大前端訓練營所做筆記和心得,若有不當之處,還望各位指出與教導,謝謝 !
一、自動化構建
簡介:
自動化指的是機器代替手工完成工作,構建可以理解為轉換,就是把一個東西轉換為另外的一些東西。總的來說自動化構建就是把開發階段寫出來的原始碼自動化去轉換成生產環境當中可以執行的程式碼或者程式,這樣的轉行的過程稱為自動化構建工作流,這樣做是儘可能讓我們脫離執行環境相容帶來的問題。在開發階段去使用一些提高效率的語法、規範和標準,比如開發網頁應用時,可以運用ECMAScript NEXT提高編碼效率和質量,利用sass增強css的可程式設計性,再去借助模板引擎抽象頁面中重複的html,這些用法大都不被瀏覽器直接支援。這些不被支援的程式碼特性轉換成能夠直接執行的程式碼,這些就可以在我們開發過程當中,通過這些方式提高我們編碼效率了。
自動化構建初體驗:
一開始是手寫css,然後再執行在瀏覽器:
我們通過sass 構建:
- 安裝sass模組,將其作為開發依賴安裝
yarn add sass --dev //或者 npm install sass --save-dev
- 使用命令將sass檔案轉換為css檔案
將scss檔案轉換後放在css資料夾下
.\node_modules\.bin\sass scss/main.scss css/style.css
- 上面的命令過於繁瑣,我們可以簡化下,使用NPM Scripts,包裝構建命令,實現自動化構建工作流的最簡方式
然後,在命令列介面使用包裝後的命令,將sass檔案轉換為css檔案:
yarn build //或者 npm run build
注意:yarn build 的run可以省略,而npm不可以
- 安裝browser-sync模組,用於啟動一個測試伺服器,使其執行我們的專案
yarn add browser-sync --dev// 或者 npm install browser-sync --save-dev
然後使用NPM Scripts,進行包裝命令,如下圖所示:
最後,在命令列介面使用包裝後的命令,啟動測試伺服器,然後喚起我們的瀏覽器,執行我們的網頁。
yarn serve //或者npm run serve
為了使我們在啟動serve命令之前,讓build命令去工作,我們可以新增一個preserve命令,會自動在serve命令執行之前去執行,這個時候再去執行serve,它就會自動化的先去執行build命令,build完成之後再去執行對應的serve
還可以在sass命令新增一個 --watch的引數,然後sass在工作時就會監聽檔案的變化,一旦程式碼當中的檔案發生改變,他就會自動被編譯
但是執行serve時,sass會等待檔案的變化阻塞serve命令的執行:
這樣導致了後面的browse sync 在後面無法工作,這種情況下就需要同時執行多個任務,這裡可以藉助於npm-run-all這個模組去實現,先安裝這個模組:
yarn add npm-run-all --dev
有了這個模組之後,就可以在scripts當中再去新增一個新的命令,這個命令叫做start,這個命令當中我們通過npm run all 裡面的run-p的這個命令同時去執行build和serve命令,在命令列當中執行:
yarn start
此時嘗試修改sass檔案裡內容,css檔案也會跟著發生變化
我們在browser-sync裡面新增--files \"css/*.css\",這個引數可以在browser-sync啟動過後去監聽專案下的一些檔案(此時是css檔案)的變化,一旦檔案發生變化,browser-sync會將這些檔案的內容自動同步到瀏覽器從而更新瀏覽器的介面,重新整理效果,避免了修改程式碼之後再手動重新整理瀏覽器了
小總結:scripts中build命令中去自動監聽sass檔案變化去編譯sass,browser-sync它啟動一個web服務,當檔案發生變化以後去重新整理瀏覽器。
常用的自動化工具:
相對於複雜的工程,npm scripts 就相對吃力,就需要更為專業的工具,用的最多的開發工具主要有:
- Grunt,它的外掛幾乎可以幫你自動化的完成任何你想要做的事情,但是由於它的工作過程是基於臨時檔案去實現的,所以構建速度較慢
- Gulp,它很好的解決了grunt構建速度非常慢的問題,它的構建過程都是在記憶體當中完成的,相對於磁碟讀寫速度就快了很多,另外預設支援同時執行多個任務,相對於graunt更加直觀易懂,外掛生態也比較完善,目前最流行的構建系統了
- FIS,相對於前兩種微核心的構建系統,FIS更像是一種捆綁套餐,它把我們專案當中一些典型的需求儘可能都整合在內部,例如可以很輕鬆的處理資源載入,模組化開發, 程式碼部署,資源優化等,初學者FIS更適合。
嚴格來說webpack是一個模組打包工具。
二、Grunt基本使用
- 新建一個空專案(空資料夾),使用yarn初始化,生成package.json檔案
yarn init --yes
- 通過yarn命令新增grunt模組
yarn add grunt
- 在專案根目錄下新增gruntfile檔案,這個檔案是Grunt 的入口檔案:
code gruntfile.js
// Grunt 的入口檔案
// 用於定義一些需要 Grunt 自動執行的任務
// 需要匯出一個函式
// 此函式接收一個 grunt 的形參,內部提供一些建立任務時可以用到的API
module.exports = grunt => {
//registerTask 去註冊一個任務,第一個引數去指定任務的名字,第二個引數去指定一個任務函式,也就是當任務發生時自動執行的函式
grunt.registerTask('foo',() =>{
console.log('hello grunt~')
})
}
- 在控制檯使用yarn grunt命令輸出:
yarn grunt foo// foo 是任務名
- 還可以新增多個任務,使用命令檢視幫助:
yarn grunt --help
// Grunt 的入口檔案
// 用於定義一些需要 Grunt 自動執行的任務
// 需要匯出一個函式
// 此函式接收一個 grunt 的形參,內部提供一些建立任務時可以用到的API
module.exports = grunt => {
//registerTask 去註冊一個任務,第一個引數去指定任務的名字,第二個引數去指定一個任務函式,也就是當任務發生時自動執行的函式
grunt.registerTask('foo',() =>{
console.log('hello grunt~')
})
grunt.registerTask('bar','任務描述',() =>{
console.log('other task~')
})
}
控制檯:
- 可以註冊一個預設任務,在yarn grunt時,將自動呼叫default
// Grunt 的入口檔案
// 用於定義一些需要 Grunt 自動執行的任務
// 需要匯出一個函式
// 此函式接收一個 grunt 的形參,內部提供一些建立任務時可以用到的API
module.exports = grunt => {
//registerTask 去註冊一個任務,第一個引數去指定任務的名字,第二個引數去指定一個任務函式,也就是當任務發生時自動執行的函式
grunt.registerTask('foo',() =>{
console.log('hello grunt~')
})
grunt.registerTask('bar','任務描述',() =>{
console.log('other task~')
})
grunt.registerTask('default',() =>{
console.log('default task~')
})
}
若是將foo和bar的任務放到自動任務的任務裡,會自動的被串聯執行
// Grunt 的入口檔案
// 用於定義一些需要 Grunt 自動執行的任務
// 需要匯出一個函式
// 此函式接收一個 grunt 的形參,內部提供一些建立任務時可以用到的API
module.exports = grunt => {
//registerTask 去註冊一個任務,第一個引數去指定任務的名字,第二個引數去指定一個任務函式,也就是當任務發生時自動執行的函式
grunt.registerTask('foo',() =>{
console.log('hello grunt~')
})
grunt.registerTask('bar','任務描述',() =>{
console.log('other task~')
})
// grunt.registerTask('default',() =>{
// console.log('default task~')
// })
grunt.registerTask('default',['foo','bar'])
}
- grunt當中支援非同步任務,我們在這個任務當中通過setTimeout模擬非同步任務:
// Grunt 的入口檔案
// 用於定義一些需要 Grunt 自動執行的任務
// 需要匯出一個函式
// 此函式接收一個 grunt 的形參,內部提供一些建立任務時可以用到的API
module.exports = grunt => {
//registerTask 去註冊一個任務,第一個引數去指定任務的名字,第二個引數去指定一個任務函式,也就是當任務發生時自動執行的函式
grunt.registerTask('foo',() =>{
console.log('hello grunt~')
})
grunt.registerTask('bar','任務描述',() =>{
console.log('other task~')
})
// grunt.registerTask('default',() =>{
// console.log('default task~')
// })
grunt.registerTask('default',['foo','bar'])
grunt.registerTask('async-task',() =>{
setTimeout(() =>{
console.log('async task working~')
},1000)
})
}
在命令列執行該非同步任務,然後會發現控制檯延遲列印:
grunt程式碼預設支援同步模式,如果需要非同步操作,必須要使用this的async方法得到一個回撥函式在非同步操作完成過後去呼叫這個回撥函式
grunt.registerTask('async-task',function(){
const done = this.async()
setTimeout(() =>{
console.log('async task working~')
done()//表示下這個任務已經完成了,grunt就知道這個是個非同步任務,會等待done的執行,直到done執行,grunt才會結束這個任務的執行
},1000)
})
Grunt標記任務失敗
如果在構建程式碼當中發生錯誤,例如需要的檔案找不到了,可以將這個任務標記為失敗的任務,在函式當中return false實現:
module.exports = grunt => {
grunt.registerTask('bad',() =>{
console.log('bad workding~')
return false
})
}
控制檯執行該任務,會顯示任務失敗,如果說這個任務是在一個任務列表當中,這個任務失敗後,後面的任務會無法執行,例如下面的程式碼,bar任務無法執行:
module.exports = grunt => {
grunt.registerTask('bad',() =>{
console.log('bad workding~')
return false
})
grunt.registerTask('foo',() =>{
console.log('foo task~')
})
grunt.registerTask('bar',() =>{
console.log('bar task~')
})
grunt.registerTask('default',['foo','bad','bar'])
}
上面的控制檯顯示bar任務沒有執行,若是使用--force會強制執行所有任務:
yarn grunt default --force
如果是個非同步任務,則沒有辦法用return false 標記,需要在done裡面加入false標記為一個失敗的任務:
grunt.registerTask('bad-async',function (){
const done = this.async()
setTimeout(() => {
setTimeout(() => {
console.log('bad async')
done(false)
},1000)
})
})
Grunt的配置方法
grunt.initConfig()是用來新增一些配置選項的API,接收一個 { } 物件形式的引數,物件的屬性名(鍵),一般與任務名保持一致; 值可以是任意型別。
module.exports = grunt => {
grunt.initConfig({
foo:'bar'
})
//有了上面的配置屬性之後,就可以在下面的任務當中使用這個配置屬性
grunt.registerTask('foo',() =>{
console.log(grunt.config('foo'))//接收一個字串引數,這個字串引數是在config當中所指定的屬性的名字
})
}
屬性值是個物件時:
module.exports = grunt => {
grunt.initConfig({
foo:{//屬性值如果是個物件的話
bar:123
}
})
//有了上面的配置屬性之後,就可以在下面的任務當中使用這個配置屬性
grunt.registerTask('foo',() =>{
console.log(grunt.config('foo.bar'))//可以拿到對應的屬性值
})
}
Grunt 多目標任務
Grunt支援多目標模式的任務,可以理解成子任務的概念,這種形式的任務在後續具體實現各種構建任務時有用:
module.exports = grunt => {
//多目標模式,可以讓任務根據配置形成多個子任務
//接收兩個引數,第一個引數是任務的名字,第二個引數是一個函式(任務執行過程所需要做的事情)
grunt.registerMultiTask('build',function(){
console.log('build task')
})
}
在控制檯執行該任務時,會報錯,表示未給我們的build任務設定一些targets,這是因為設定多目標任務時,我們需要為這種多目標的任務配置不同的目標,通過grunt的init方法中的config去配置:
module.exports = grunt => {
//多目標模式,可以讓任務根據配置形成多個子任務
grunt.initConfig({
build:{
css:'1',
js:'2'
}
})
//接收兩個引數,第一個引數是任務的名字,第二個引數是一個函式(任務執行過程所需要做的事情)
grunt.registerMultiTask('build',function(){
console.log('build task')
})
}
控制檯執行後會發現有兩個子任務執行了,其實不叫子任務,在grunt當中叫多目標,也就是build任務有兩個目標,一個js目標,一個css目標
若是想單獨執行一個目標,可以在命令列後面加上對應的目標名:
yarn grunt build:css
在我們這個任務當中,可以通過this拿到當前目標的名稱,通過data拿到這個目標對應的資料,然後在控制檯執行任務的對應目標:
module.exports = grunt => {
//多目標模式,可以讓任務根據配置形成多個子任務
grunt.initConfig({
build:{
css:'1',
js:'2'
}
})
//接收兩個引數,第一個引數是任務的名字,第二個引數是一個函式(任務執行過程所需要做的事情)
grunt.registerMultiTask('build',function(){
console.log(`tartget:${this.target},data:${this.data}`)
})
}
在build裡每個屬性的鍵都會成為目標,除了指定的option以外
module.exports = grunt => {
//多目標模式,可以讓任務根據配置形成多個子任務
grunt.initConfig({
build:{
//在option當中指定的資訊會作為這個任務的配置選項出現,控制檯列印不出options內容
options:{
foo:'bar'
},
css:'1',
js:'2'
}
})
//接收兩個引數,第一個引數是任務的名字,第二個引數是一個函式(任務執行過程所需要做的事情)
grunt.registerMultiTask('build',function(){
console.log(this.options())//要想列印options裡面的內容,需要用this的options方法
console.log(`tartget:${this.target},data:${this.data}`)
})
}
目標的屬性值也可為options物件:
module.exports = grunt => {
//多目標模式,可以讓任務根據配置形成多個子任務
grunt.initConfig({
build:{
//在option當中指定的資訊會作為這個任務的配置選項出現
options:{
foo:'bar'
},
//如果說目標的配置也是一個物件的話,這個屬性也可以新增options,這個options能覆蓋掉物件裡的options
css:{
options:{
foo:'baz'
}
},
js:'2'
}
})
//接收兩個引數,第一個引數是任務的名字,第二個引數是一個函式(任務執行過程所需要做的事情)
grunt.registerMultiTask('build',function(){
console.log(this.options())
console.log(`tartget:${this.target},data:${this.data}`)
})
}
Grunt外掛的使用
grunt.loadNpmTaks() 載入外掛提供的一些任務:
grunt.loadNpmTasks('grunt-contrib-clean'),載入grunt-contrib-clean外掛,這個外掛用來清除在專案開發過程中產生的臨時檔案。
grunt外掛常用命名方式:grunt-contrib-pluginname
使用grunt外掛執行外掛的任務時,完整外掛名稱後面的pluginname,其實就是提供的任務名稱:yarn grunt pluginname //或者 npm run grunt pluginname
- grunt-contrib-clean:
安裝: $ yarn add grunt-contrib-clean // 或者 npm install grunt-contrib-clean
module.exports = grunt => {
grunt.initConfig({
clean: {
temp: 'temp/app.js', // 所要清除的檔案的具體路徑
tempTxt: 'temp/*.txt', // 使用萬用字元*,刪除所有txt檔案
tempFiles: 'temp/**' // 使用**的形式,刪除temp整個資料夾
}
})
grunt.loadNpmTasks('grunt-contrib-clean')
}
使用:外掛中的任務需要在initConfig()中進行配置。
module.exports = grunt => {
grunt.initConfig({
clean:{
// temp:'temp/app.js'//清除temp下的app.js檔案
// temp:'temp/*.txt'//清除temp下所有的txt檔案
temp:'temp/**'//清除temp目錄下所有的檔案
}
})
grunt.loadNpmTasks('grunt-contrib-clean')
}
temp檔案被清除
- Grunt-sass:是一個npm模組,在內部通過npm依賴sass,他需要一個npm提供sass模組進行支援,因此兩個模組都需要安裝:
安裝外掛模組:yarn add grunt-sass 或者 npm install grunt-sass sass --save-dev
配置sass:
module.exports = grunt => {
grunt.initConfig({
sass:{
main:{//需要指定輸入檔案,以及最終輸出的css檔案路徑
files:{
//屬性名(鍵)為,需要輸出的css的路徑
//屬性值為需要輸入的scss檔案的路徑
'dist/css/main.css':'src/scss/main.scss'
}
}
}
})
grunt.loadNpmTasks('grunt-sass')
}
此時執行控制檯報錯,需要傳入一個implementation的選項,這是一個用來指定grunt-sass當中使用哪一個模組去處理sass的編譯:
為sass新增option:
const sass = require('sass')//匯入sass模組
module.exports = grunt => {
grunt.initConfig({
sass:{
options:{
sourceMap:true,//生成對應的sourceMap檔案
implementation:sass//把sass模組傳入屬性當中
},
main:{//需要指定輸入檔案,以及最終輸出的css檔案路徑
files:{
//屬性名(鍵)為,需要輸出的css的路徑
//屬性值為需要輸入的scss檔案的路徑
'dist/css/main.css':'src/scss/main.scss'
}
}
}
})
grunt.loadNpmTasks('grunt-sass')
}
控制檯命令執行:
可以在資料夾看到編譯的scss檔案:
更多的可以在grunt-sass官方文件看到。
- grunt-babel 外掛,用來編譯ES6語法,它需要使用 Babel 的核心模組 @babel/core,以及 Babel 的預設@babel/preset-env
安裝外掛模組:
yarn add grunt-babel @babel/core @babel/preset-env --dev
此時又需要grunt.loadNpmTasks()來新增babel任務,隨著grunt-file越來越複雜,這個方法會用的越來越多,此時社群當中有一個模組(load-grunt-tasks)可以減少這個方法的使用:
- 安裝模組:
yarn add load-grunt-tasks --dev //或者 npm install load-grunt-tasks -save-dev
- 基本使用:
const loadGruntTasks = require('load-grunt-tasks') module.exports = grunt => { loadGruntTasks(grunt) // 自動載入所有的 grunt 外掛中的任務 }
安裝babel模組後,修改對應的js:
const loadGruntTasks = require('load-grunt-tasks')
module.exports = grunt => {
grunt.initConfig({
babel: {//將ECMAScript最新特性轉換為js
options: {
sourceMap: true,
// //你需要轉換哪些特性,把這些特性打包形成了preset
presets: ['@babel/preset-env'] //env 會預設根據最新的es特性去做對應的轉換
},
main: {
files: {
'dist/js/app.js': 'src/js/app.js'
}
}
}
})
loadGruntTasks(grunt) // 自動載入所有的 grunt 外掛中的任務
}
執行yarn grunt babel
此時js資料夾裡的js檔案就是自動把原來寫的es6的程式碼自動轉為es5的方式,還生成了對應的sourceMap檔案:
- grunt-contrib-watch外掛,是指當檔案發生改變時,可以實現自動跟蹤編譯
安裝外掛模組
yarn add grunt-contrib-watch --dev // 或者 npm install grunt-contrib-watch -save-dev
基本使用:
const sass = require('sass')//匯入sass模組
const loadGruntTasks = require('load-grunt-tasks')
module.exports = grunt => {
grunt.initConfig({
sass:{
options:{
sourceMap:true,//生成對應的sourceMap檔案
implementation:sass//把sass模組傳入屬性當中
},
main:{//需要指定輸入檔案,以及最終輸出的css檔案路徑
files:{
//屬性名(鍵)為,需要輸出的css的路徑
//屬性值為需要輸入的scss檔案的路徑
'dist/css/main.css':'src/scss/main.scss'
}
}
},
babel:{//將ECMAScript最新特性轉換為js
options:{
sourceMap:true,
//你需要轉換哪些特性,把這些特性打包形成了preset
presets:['@babel/preset-env']//env 會預設根據最新的es特性去做對應的轉換
},
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.registerTask('default',['sass','babel','watch'])
}
執行命令
yarn grunt
三、Gulp
Gulp核心特點是高效,易用。
- 建立資料夾,並初始化package.json包管理檔案:
$ mkdir project-name $ cd project-name $ yarn init --yes # or npm init -y
-
安裝gulp模組,同時會安裝一個Gulp CLI的命令
$ yarn add gulp --dev # or npm install gulp --save-dev
- 使用命令新增一個 gulp 的 入口檔案 gulpfile.js ,並進行編碼
$ code gulpfile.js
在gulpfile.js檔案中定義一些Gulp需要執行的一些Gulp的構建任務
// gulp的入口檔案,因為這個檔案時執行在node.js的環境當中,所以說可以在在這個檔案當中執行commentJS規範
//這個檔案定義構建任務的方式就是通過匯出成員的方式去定義
exports.foo = () =>{
console.log('foo task working~')
}
執行結果:yarn gulp foo 程式碼指定執行foo這個任務
報錯:我們的foo任務執行沒有完成,問我們是否忘記去標識這個任務的結束,這是因為在最新的gulp當中取消了同步程式碼模式,約定每一個任務都必須是一個非同步的任務,當我們的任務執行完成過後需要通過呼叫回撥函式或者其他的方式去標記任務已經完成,所以說我們在解決這個問題這一塊,需要做的事情就是手動去呼叫一個回撥函式,這個回撥函式我們可以在程式碼當中通過foo這個函式的形式引數得到:
//這個回撥函式我們可以在程式碼當中通過foo這個函式的形式引數得到,接收一個done的引數,這個done就是一個函式,可以在任務執行過後呼叫這個函式
exports.foo = done =>{
console.log('foo task working~')
done()//標識任務完成
}//這就是我們在gulp當中去定義一個任務的操作方式
執行結果:
標識已經完成foo任務。
如果說你的任務方式是default的話,它會作為gulp的預設任務出現
exports.default = done => {
console.log('default task working~')
done()
}
使用 yarn gulp 命令執行,會直接執行預設任務
在gulp4.0以前,我們註冊任務時需要通過gulp模組裡面的一個方法去實現,通過require的一個方法:
const gulp = require('gulp')
gulp.task('bar',done =>{
console.log('bar working~')
done()
})//註冊了一個叫bar的任務,這種方式不被推薦
Gulp組合任務的API:series和parallel,可以建立並行任務和序列任務,使用series實現序列任務,使用parallel實現並行任務,使用parallel實現並行任務,使用這兩種方法對實際建立構建工作流很有用。
const {series,parallel} = require('gulp')
//這些未被匯出的函式可以理解成私有的任務,各自模擬了一個需要執行1秒的任務,並不能通過gulp去直接執行他們,通過gulp的series和parallel這兩個API去把這些任務組合
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是一個函式,每一個引數都可以是一個任務
exports.foo = series(task1,task2,task3)
//parallel是一個函式,每一個引數都可以是一個任務
exports.bar = parallel(task1,task2,task3)
序列任務一般用在專案部署上,即需要先執行編譯的任務,再進行部署。並行任務一般用於同時開始編譯js和css檔案時。
Gulp的非同步任務
我們呼叫一個非同步函式時通常沒有直接明確這個函式是否是完成的,都是在函式內部或者事件的方式通知外部這個函式執行完成,我們在非同步任務當中同樣面臨如何通知gulp我們的完成情況這樣一個問題,針對這個問題gulp有很多解決方法,以下是常用的幾種方式:
- callback_error
exports.callback = done => {
console.log('callback')
done()//呼叫done表示這個任務執行完成
}//這個回撥函式它與node當中回撥函式是同樣的標準都是一種錯誤優先的回撥函式,也就是說當我們想在執行過程中報出一個錯誤去阻止剩下的任務執行的時候,這個時候我們可以通過給回撥函式的第一個回撥引數去指定一個錯誤物件就可以了:
exports.callback_error = done =>{
console.log('callback task~')
done(new Error('task failed!'))
}
- promise
//在gulp當中同樣支援promise的方式
exports.promise = () => {
console.log('promise task~')
return Promise.resolve()//返回一個成功的promise,一旦當我們返回的物件resolve了就意味著我們這個任務結束了,resolve的時候我們不需要返回任何的值,因為gulp當中它會忽略掉這個值
}
exports.promise_error = () =>{
console.log('promise task~')
return Promise.reject(new Error('task failed~'))//gulp會認為這是一個失敗的任務,它同樣會結束後續所有的任務的執行
}
- async
const timeout = time =>{//把setTimeout包裝成promise函式
return new Promise(resolve =>{
setTimeout(resolve,time)
})
}
exports.async = async() =>{//在async當中await這個函式
await timeout(1000)//在這個位置等待promis的resolve,resolve完成過後就會執行下面的程式碼
console.log('async task~')
}
-
通過stream的方式處理非同步任務最為常見的,因為我們的構建系統大都是在處理檔案,所以這種方式也是最常用到的一種
exports.stream = () =>{
//通過fs的createReadStream方法讀取檔案的檔案流
const readStream = fs.createReadStream('package.json')
//建立寫入檔案的檔案流
const writeStream = fs.createWriteStream('temp.txt')
//把readStream通過pipe的方式導到writeStream當中,可以理解為從一個水池子往另外一個水池子倒水,就會起到檔案複製的作用
readStream.pipe(writeStream)
return readStream
}
stream當中有一個end事件,當檔案流讀取完成過後就會觸發end事件,gulp就瞭解了這個任務已經完成了
exports.stream = done =>{
//通過fs的createReadStream方法讀取檔案的檔案流
const readStream = fs.createReadStream('package.json')
//建立寫入檔案的檔案流
const writeStream = fs.createWriteStream('temp.txt')
//把readStream通過pipe的方式導到writeStream當中,可以理解為從一個水池子往另外一個水池子倒水,就會起到檔案複製的作用
readStream.pipe(writeStream)
readStream.on('end',() =>{//gulp為stream註冊了一個end事件,這個事件當中結束了這個任務執行,
done()//呼叫done函式模擬gulp當中結束這個任務的操作
})
}
以上是gulp當中經常會用到的處理非同步流程的操作
Gulp構建過程核心工作原理
構建過程大多數情況下都是將檔案讀出來然後進行一些轉換,最後去寫入到另外一個位置,在沒有構建系統的情況下,我們也就是人工去按照這個過程去做的。例如我們壓縮一個css檔案,需要把程式碼複製出來,然後到壓縮工具當中壓縮一下,然後把壓縮過後的結果貼上到一個新的檔案當中,這是一個手動的過程,其實通過程式碼的方式解決也是類似的,通過對底層的原始流的api去實現一下這樣一個過程:
const fs = require('fs')
exports.default = () =>{
//檔案讀取流
const read = fs.createReadStream('normalize.css')
//檔案寫入流
const write = fs.createWriteStream('normalize.min.css')
//把讀取出來的檔案流匯入寫入檔案流
read.pipe(write)
return read
}
將normalize.css檔案複製到了normalize.min.css檔案當中
現在我們將檔案讀出來經過轉換後再寫入檔案:
const fs = require('fs')
const { Transform } = require('stream')//匯入stream模組當中的transform這個型別,這個型別可以建立一個檔案轉換流物件
exports.default = () =>{
//檔案讀取流
const read = fs.createReadStream('normalize.css')
//檔案寫入流
const write = fs.createWriteStream('normalize.min.css')
//檔案轉換流
const transform = new Transform({
transform:(chunk,encoding,callback) =>{
//核心轉換過程實現
//chunk =>讀取流中讀取的內容(Buffer)
//因為讀出來是一個自己陣列,通過tostring的方式轉換為字串
const input = chunk.toString()
//替換掉空白字元,然後替換掉css當中的註釋
const output = input.replace(/\s+/g,'').replace(/\/\*.+?\//g,'')
//將output返回出去,callback是一個錯誤優先的回撥函式,如果沒有發生錯誤的話,先傳入null
callback(null,output)
}
})
//把讀取出來的檔案流匯入寫入檔案流
read
.pipe(transform)//到轉換流當中完成轉換
.pipe(write)//寫入流
return read
}
轉換後的檔案:
Gulp檔案操作API
相對於底層node的api,它更強大也更容易使用,對於獨立的檔案轉換流,我們一般使用強大的外掛。我們在 實際去通過gulp建立構建任務時的流程,就是先通過src方法去建立一個讀取流,然後在藉助於外掛的轉換流實現檔案加工,最後通過gulp提供的dest方法建立一個寫入流,從而寫入目標檔案。
先複製檔案:
const {src,dest} = require('gulp')
exports.default = () => {
//通過src方法建立一個檔案的讀取流,將讀取流return出去,這樣gulp能控制任務完成
return src('src/normalize.css')
//通過pipe的方式匯出到dest所建立的寫入流當中
.pipe(dest('dist'))
}
檔案複製到dist中的norma.css中了:
使用gulp外掛當中提供的一些裝換流:
const {src,dest} = require('gulp')
const cleanCss = require('gulp-clean-css')
const rename = require('gulp-rename')
exports.default = () => {
//通過src方法建立一個檔案的讀取流,將讀取流return出去,這樣gulp能控制任務完成
return src('src/*.css')//src下所有的css檔案
//pipe到cleanCss當中提供的轉換流
.pipe(cleanCss())
//若是要新增額外的多個轉換的操作,例如我們新增gulp-rename外掛
.pipe(rename({extname:'.min.css'}))//pipe到轉換流當中,這個rename可以指定一個extname引數,這個引數用於指定重新命名的副檔名為.min.css
//通過pipe的方式匯出到dest所建立的寫入流當中
.pipe(dest('dist'))
}
//gulp-clean-css 這個外掛提供了壓縮css程式碼的轉換流
//dist目錄下出現了bootstrap.min.css和normalize.min.css,意味著第二種轉換流外掛也正常工作了
安裝gulp-clean-css外掛:
、
執行:
可以看到src下所有的css檔案都被轉換壓縮為dist目錄下的css檔案:
若是要新增額外的多個轉換的操作,例如我們新增gulp-rename外掛:
執行後:
src下的css檔案生成了以.min.css為字尾的副檔名的檔案:
四、Gulp案例:樣式編譯
專案案例目錄:
在gulpfile.js檔案裡構建任務:
gulp-sass:gulp-sass 模組 用來將 sass 樣式檔案轉換成 css 樣式檔案,同時 gulp-sass 模組 需要npm提供sass模組進行支援,因此兩個模組都需要安裝。
yarn add gulp-sass --dev
將sass檔案轉換成css檔案:
const {src,dest} = require('gulp')
//gulp-sass 模組 用來將 sass 樣式檔案轉換成 css 樣式檔案,同時 gulp-sass 模組 需要npm提供sass模組進行支援,因此兩個模組都需要安裝。
const sass = require('gulp-sass')
//sass在轉換的時候,前面有下劃線開頭的檔名不會被轉換,認為這些是我們主檔案當中所依賴的檔案,會忽略掉。
const style = () => {//style是一個私有的任務,並不能通過gulp去執行
return src('src/assets/styles/*scss',{base:'src'})//base的作用是能保證原來的目錄結構,此時會把src後面的目錄結構保留下來
//基本上每個外掛都會提供一個函式,每個函式會返回一個轉換流
.pipe(sass({outputStyle:'expanded'}))//轉換成將樣式程式碼完全展開的格式
.pipe(dest('dist'))
}
//通過module.exports匯出一個物件
module.exports = {
style
}
sass在轉換的時候,前面有下劃線開頭的檔名不會被轉換,認為這些是我們主檔案當中所依賴的檔案,會忽略掉。
五、Gulp案例:指令碼編譯
gulp-babel
gulp-babel 模組,用來將 ES6 語法轉換成 ES5 語法,但是 gulp-babel 模組只是喚醒 @babel/core 模組中的轉換過程,並不會自動的去呼叫 @babel/core模組中的轉換方法,因此,需要同時安裝 @babel/core模組。並且,若需要將ECMAScript 所有的新特性進行轉換時,還需要安裝 @babel/preset-env 模組。
安裝:
yarn add gulp-babel @babel/core @babel/preset-env --dev
將ES6語法編譯為ES5語法
const {src,dest} = require('gulp')
//gulp-sass 模組 用來將 sass 樣式檔案轉換成 css 樣式檔案,同時 gulp-sass 模組 需要npm提供sass模組進行支援,因此兩個模組都需要安裝。
const sass = require('gulp-sass')
//sass在轉換的時候,前面有下劃線開頭的檔名不會被轉換,認為這些是我們主檔案當中所依賴的檔案,會忽略掉。
//使用babel 依賴將ES6語法編譯為ES5語法
const babel = require('gulp-babel')
const style = () => {
return src('src/assets/styles/*scss',{base:'src'})
.pipe(sass({outputStyle:'expanded'}))
.pipe(dest('dist'))
}
const script = () =>{
return src('src/assets/scripts/*.js',{base:'src'})
//babel預設只是ECMAScript的轉換平臺,平臺是不做任何事情的,它只是提供一個環境,具體去做轉換的實際上是babel裡面它內部裡的外掛,而preset就是一些外掛的集合,preset-env就是一些最新的整體的特性進行打包,
//使用它的話就把所有的最新的特性做轉換,也可以根據需要做對應的轉換外掛然後在這個地方制定babel下對應的外掛,這樣的話只會轉換對應的特性
.pipe(babel({presets:['@babel/preset-env']}))
.pipe(dest('dist'))
}
module.exports = {
style,
script
}
六、Gulp案例:頁面模板編譯
模板檔案就是html檔案,在這些html檔案當中我們為了可以去讓頁面當中重複的地方被抽象出來,這使用的模板引擎叫做swig:
gulp-swig用來將使用swig模板引擎的html檔案轉換成正常的html檔案,安裝:
yarn add gulp-swig --dev
const {src,dest,parallel} = require('gulp')
const swig = require('gulp-swig')
const sass = require('gulp-sass')
//sass在轉換的時候,前面有下劃線開頭的檔名不會被轉換,認為這些是我們主檔案當中所依賴的檔案,會忽略掉。
//使用babel 依賴將ES6語法編譯為ES5語法
const babel = require('gulp-babel')
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(sass({outputStyle:'expanded'}))
.pipe(dest('dist'))
}
const script = () =>{
return src('src/assets/scripts/*.js',{base:'src'})
//babel預設只是ECMAScript的轉換平臺,平臺是不做任何事情的,它只是提供一個環境,具體去做轉換的實際上是babel裡面它內部裡的外掛,而preset就是一些外掛的集合,preset-env就是一些最新的整體的特性進行打包,
//使用它的話就把所有的最新的特性做轉換,也可以根據需要做對應的轉換外掛然後在這個地方制定babel下對應的外掛,這樣的話只會轉換對應的特性
.pipe(babel({presets:['@babel/preset-env']}))
.pipe(dest('dist'))
}
const page = () => {
return src('src/*.html',{base:'src'})
//將編寫的data自動的根據模板檔案填入然後轉化成html檔案,這樣可以將網頁當中經常寫死的資料提取出來在程式碼當中配置
.pipe(swig({data:data}))
.pipe(dest('dist'))
}
//將三個任務組合起來並行執行,這樣可以提高構建效率
const compile = parallel(style,script,page)
module.exports = {
compile
}
七、Gulp案例:圖片和字型檔案轉換
gulp-imagemin模組,用來對圖片進行壓縮後,轉換到目標目錄:
yarn add gulp-imagemin --dev//或者 npm install gulp-imagemin --save-dev
將圖片壓縮後編譯:
const { src, dest, parallel } = require('gulp')
// 將圖片進行壓縮後轉換
const imagemin = require('gulp-imagemin')
// 圖片壓縮編譯
const image = () => {
return src('src/assets/images/**', { base: 'src' })
.pipe(imagemin())
.pipe(dest('dist'))
}
// 字型檔案編譯
const font = () => {
// 字型檔案可以直接拷貝進目標檔案,但存在 .svg檔案,因此可以使用imagemin() 進行轉換
return src('src/assets/fonts/**', { base: 'src' })
.pipe(imagemin())
.pipe(dest('dist'))
}
// 建立並行任務,完成 src目錄下面需要編譯的檔案
const compile = parallel(style, script, page, image, font)
module.exports = {
compile
}
八、其他檔案及檔案清除
安裝del外掛,這個外掛不是gulp的單獨外掛:
yarn add del --dev
const {src,dest,parallel,series} = require('gulp')
const del = require('del')
const swig = require('gulp-swig')
const sass = require('gulp-sass')
const babel = require('gulp-babel')
const imagemin = require('gulp-imagemin')
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(sass({outputStyle:'expanded'}))
.pipe(dest('dist'))
}
const clean = () => {
return del(['dist'])//返回的是promise,意味著delete完成後,它可以標記clean任務執行完成
}
const script = () =>{
return src('src/assets/scripts/*.js',{base:'src'})
.pipe(babel({presets:['@babel/preset-env']}))
.pipe(dest('dist'))
}
const page = () => {
return src('src/*.html',{base:'src'})
.pipe(swig({data:data}))
.pipe(dest('dist'))
}
//字型檔案編譯
const font = () =>{
// 字型檔案可以直接拷貝進目標檔案,但存在 .svg檔案,因此可以使用imagemin() 進行轉換
return src('src/assets/fonts/**',{base:'src'})
.pipe(imagemin())
.pipe(dest('dist'))
}
//圖片壓縮編譯
const image = () =>{
return src('src/assets/images/**',{base:'src'})
.pipe(imagemin())
.pipe(dest('dist'))
}
//這是個額外拷貝的任務
const extra = () =>{
return src('public/**',{base:'public'})
.pipe(dest('dist'))
}
//將三個任務組合起來並行執行,這樣可以提高構建效率
const compile = parallel(style,script,page,image,font)
//使用series序列執行任務,先清除檔案,再執行其它
const build = series(clean,parallel(compile,extra))
module.exports = {
compile,
build
}
九、Gulp案例:自動載入外掛
手動的方式載入外掛,require的操作會非常多,不太利於後期維護程式碼,可以通過外掛解決這個問題:
Gulp-load-plugins:
yarn add gulp-load-plugins --dev
使用:
const loadPlugins = require('gulp-load-plugins')//匯出的是一個方法
const plugins = loadPlugins() //plugins是一個物件,所有的外掛都是這個物件下面的屬性
const style = () => {
return src('src/assets/styles/*scss',{base:'src'})
//plugins.xxx xxx 代表外掛的名稱,即去掉 gulp- 字首後的名稱,若有多級,採用駝峰命名
.pipe(plugins.sass({outputStyle:'expanded'}))
.pipe(dest('dist'))
}
module.exports = {
style
}
十、Gulp案例:自動化構建案例
除了對檔案構建操作以外,我們這裡還需要一個開發伺服器,用於去在開發階段除錯我們的應用,用於gulp去啟動和管理我們的應用,這樣的話我們就可以後續配合我們其它的一些構建任務去實現程式碼修改過後自動編譯,並且自動重新整理瀏覽器頁面,這樣就能大大提高開發效率:
browser-sync:
browser-sync 模組,提供開發伺服器,他支援修改過後自動熱更新到瀏覽器中,讓我們可以即時的看到最新頁面效果。它並不是gulp外掛,只是可以通過 gulp 進行管理。安裝外掛模組:
yarn add browser-sync --dev // 或者 npm install browser-sync --save-dev
const browserSync = require('browser-sync')
const bs = browserSync.create()//自動建立一個開發伺服器
//把開發伺服器定義到單獨的一個任務當中使用
const serve = () => {
//通過bs的init方法去初始化一下web伺服器的一些相關配置,最核心的配置就是server
bs.init({
notify:false,//關掉右上角的小提示
port:2080//埠
open:false,//取消自動開啟瀏覽器
files:'dist/**',//可以指定一個字串,這個字串就是用來去被browser-sync監聽的路徑萬用字元,這裡面的檔案發生改變過後,browser-sync自動更新瀏覽器
//server當中需要去指定一下我們的網站的根目錄,也就是web伺服器它需要幫你把哪個目錄作為網站的根目錄,通過baseDir去設定
server:{
baseDir:'dist'//src下是未加工的程式碼,所以用dist
//加一個特殊的路由,讓它對於這種/node_modules開頭的網頁請求的話,我們都給它指到同一個目錄下面
routes:{
//這裡面會優先於我們baseDir的配置,先看routes裡面有沒有配置,再看baseDir下對應的檔案,鍵是我們請求的字首,然後我們把它指到目錄下面
'/node_modules':'node_modules'//相對於我們網站根目錄下的node_modules
}
}
})
}
module.exports = {
compile,
build,
serve
}
現在我們來監視src下的檔案的變化,一旦他們發生變化過後,我們不是直接去重新整理瀏覽器而是去執行我們的構建任務:
監視變化以及構建過程優化:
Gulp提供了一個watch的api,會自動監視一個檔案路徑的萬用字元,然後根據這個檔案的變化決定是否要重新去執行某個任務。
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() //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 style = () => {
return src('src/assets/styles/*scss',{base:'src'})
//plugins.xxx xxx 代表外掛的名稱,即去掉 gulp- 字首後的名稱,若有多級,採用駝峰命名
.pipe(plugins.sass({outputStyle:'expanded'}))
.pipe(dest('dist'))
}
//把開發伺服器定義到單獨的一個任務當中使用
const serve = () => {
watch('src/assets/styles/*scss',style)//指定兩個引數,第一個引數路徑萬用字元,第二個是對應的任務
watch('src/assets/scripts/*.js',script)
watch('src/*.html',page)
watch('src/assets/fonts/**',font)
watch('src/assets/images/**',image)
watch('public/**',extra)
//通過bs的init方法去初始化一下web伺服器的一些相關配置,最核心的配置就是server
bs.init({
notify:false,//關掉右上角的小提示
port:2080//埠
open:false,//取消自動開啟瀏覽器
files:'dist/**',//可以指定一個字串,這個字串就是用來去被browser-sync監聽的路徑萬用字元,這裡面的檔案發生改變過後,browser-sync自動更新瀏覽器
//server當中需要去指定一下我們的網站的根目錄,也就是web伺服器它需要幫你把哪個目錄作為網站的根目錄,通過baseDir去設定
server:{
baseDir:'dist'//src下是未加工的程式碼,所以用dist
//加一個特殊的路由,讓它對於這種/node_modules開頭的網頁請求的話,我們都給它指到同一個目錄下面
routes:{
//這裡面會優先於我們baseDir的配置,先看routes裡面有沒有配置,再看baseDir下對應的檔案,鍵是我們請求的字首,然後我們把它指到目錄下面
'/node_modules':'node_modules'//相對於我們網站根目錄下的node_modules
}
}
})
}
const clean = () => {
return del(['dist'])//返回的是promise,意味著delete完成後,它可以標記clean任務執行完成
}
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:data}))
.pipe(dest('dist'))
}
//字型檔案編譯
const font = () =>{
// 字型檔案可以直接拷貝進目標檔案,但存在 .svg檔案,因此可以使用imagemin() 進行轉換
return src('src/assets/fonts/**',{base:'src'})
.pipe(plugins.imagemin())
.pipe(dest('dist'))
}
//圖片壓縮編譯
const image = () =>{
return src('src/assets/images/**',{base:'src'})
.pipe(plugins.imagemin())
.pipe(dest('dist'))
}
//這是個額外拷貝的任務
const extra = () =>{
return src('public/**',{base:'public'})
.pipe(dest('dist'))
}
//將三個任務組合起來並行執行,這樣可以提高構建效率
const compile = parallel(style,script,page,image,font)
//使用series序列執行任務,先清除檔案,再執行其它
const build = series(clean,parallel(compile,extra))
module.exports = {
compile,
build,
serve
}
哪些任務在開發階段不需要執行,哪些需要?然後進行優化:圖片檔案只是做了個畫質無損的壓縮,還有文字和公共的部分都做了一個壓縮,可以在開發過程中減少構建次數
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() //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 style = () => {
return src('src/assets/styles/*scss',{base:'src'})
//plugins.xxx xxx 代表外掛的名稱,即去掉 gulp- 字首後的名稱,若有多級,採用駝峰命名
.pipe(plugins.sass({outputStyle:'expanded'}))
.pipe(dest('dist'))
}
//把開發伺服器定義到單獨的一個任務當中使用
const serve = () => {
watch('src/assets/styles/*scss',style)//指定兩個引數,第一個引數路徑萬用字元,第二個是對應的任務
watch('src/assets/scripts/*.js',script)
watch('src/*.html',page)
// watch('src/assets/fonts/**',font)
// watch('src/assets/images/**',image)
// watch('public/**',extra)
watch([
'src/assets/fonts/**',
'src/assets/images/**',
'public/**'
],bs.reload)//這些檔案發生變化過後它自動更新瀏覽器
//通過bs的init方法去初始化一下web伺服器的一些相關配置,最核心的配置就是server
bs.init({
notify:false,//關掉右上角的小提示
port:3000,//埠
// open:false,//取消自動開啟瀏覽器
files:'dist/**',//可以指定一個字串,這個字串就是用來去被browser-sync監聽的路徑萬用字元,這裡面的檔案發生改變過後,browser-sync自動更新瀏覽器
//server當中需要去指定一下我們的網站的根目錄,也就是web伺服器它需要幫你把哪個目錄作為網站的根目錄,通過baseDir去設定
server:{
//指定一個陣列,當請求過來之後,回到陣列中第一個目錄去找,找不到這個檔案的話就依次往後去找
baseDir:['dist','src','public'],
//加一個特殊的路由,讓它對於這種/node_modules開頭的網頁請求的話,我們都給它指到同一個目錄下面
routes:{
//這裡面會優先於我們baseDir的配置,先看routes裡面有沒有配置,再看baseDir下對應的檔案,鍵是我們請求的字首,然後我們把它指到目錄下面
'/node_modules':'node_modules'//相對於我們網站根目錄下的node_modules
}
}
})
}
const clean = () => {
return del(['dist'])//返回的是promise,意味著delete完成後,它可以標記clean任務執行完成
}
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:data}))
.pipe(dest('dist'))
}
//字型檔案編譯
const font = () =>{
// 字型檔案可以直接拷貝進目標檔案,但存在 .svg檔案,因此可以使用imagemin() 進行轉換
return src('src/assets/fonts/**',{base:'src'})
.pipe(plugins.imagemin())
.pipe(dest('dist'))
}
//圖片壓縮編譯
const image = () =>{
return src('src/assets/images/**',{base:'src'})
.pipe(plugins.imagemin())
.pipe(dest('dist'))
}
//這是個額外拷貝的任務
const extra = () =>{
return src('public/**',{base:'public'})
.pipe(dest('dist'))
}
//將三個任務組合起來並行執行,這樣可以提高構建效率
const compile = parallel(style,script,page)
//上線之前執行的任務
const build = series(clean,parallel(compile,image,font,extra))
const develop = series(compile,serve)
module.exports = {
compile,
build,
serve,
develop
}
useref檔案引用處理:
對於dist檔案下還有一些小問題,dist裡面的一些html裡面可能會有一些node_modules裡面的一些依賴,這裡面的一些檔案並沒有拷貝到dist目錄下面,dist目錄部署上線的話肯定會出現問題,會找不到下面的bootsrap的這些檔案,開發階段沒有出現問題的原因是因為我們 在serve命令裡面已經做了一個路由的對映,並不能線上上環境去做,還需要單獨處理,常見的是用useref處理:
useref 模組,會自動處理 HTML檔案中的構建註釋,但是隻有在編譯後的 HTML檔案中才會存在構建註釋,因此,這個外掛提供的 useref 任務,需要在編譯後再執行。
構建註釋 ,即包含一個開始的 build 和一個結束的 endbuild,會將裡面包裹的多個標籤,合併一個檔案,如下面的 vendor.css
<!-- build:css assets/styles/vendor.css -->
<link rel="stylesheet" href="/node_modules/bootstrap/dist/css/bootstrap.css">
<!-- endbuild -->
<!-- build:css assets/styles/main.css -->
<link rel="stylesheet" href="assets/styles/main.css">
<!-- endbuild -->
安裝外掛模組
$ yarn add gulp-useref --dev # or npm install gulp-useref --save-dev
經過useref過後,會把構建註釋當中引入的一些資源全部合併到同一個檔案當中:
const {src,dest,parallel,series,watch} = require('gulp')
const loadPlugins = require('gulp-load-plugins')//匯出的是一個方法
const plugins = loadPlugins() //plugins是一個物件,所有的外掛都是這個物件下面的屬性
const useref = () => {
return src('dist/*.html',{base:'dist'})//找dist下面的html
.pipe(plugins.useref({searchPath:['dist','.']}))//建立一個轉換流,把程式碼當中的構建註釋去做對應的轉換,searchPath中的是註釋中引用的根目錄
.pipe(dest('dist'))
}
module.exports = {
compile,
build,
serve,
develop,
useref
}
構建註釋當中引用的資源全部合併到同一個檔案當中,比如第一個引用的css檔案全部合併到了vendor.css當中
檔案壓縮:
useref把我們需要的檔案全部拿過來了,現在將這些檔案壓縮,三種不同型別的檔案:html,js,css檔案,先安裝各個型別的壓縮外掛,gulp-htmlmin, gulp-uglify ,gulp-clean-css分別對應壓縮html,js,css檔案的外掛:
yarn add gulp-htmlmin gulp-uglify gulp-clean-css --dev
Gulp-if:
安裝gulp-if模組,可以用來判斷讀取流中的檔案型別,在其內部會自動建立轉換流:
$ yarn add gulp-if --dev # or npm install gulp-if --save-dev
const { src, dest } = require('gulp')
// 自動載入全部外掛
const loadPlugins = require('gulp-load-plugins')
// 返回一個 plugins的物件
const plugins = loadPlugins()
const useref = () => {
return src('dist/*.html',{base:'dist'})//找dist下面的html
.pipe(plugins.useref({searchPath:['dist','.']}))//建立一個轉換流,把程式碼當中的構建註釋去做對應的轉換,searchPath中的是註釋中引用的根目錄
//html js css
.pipe(plugins.if(/\.js$/,plugins.uglify()))//是否以js結尾
.pipe(plugins.if(/\.css$/,plugins.cleanCss()))//是否以css結尾
.pipe(plugins.if(/\.html$/,plugins.htmlmin({
collapseWhitespace:true,
minifyCss:true,
minifyJs:true
})))//是否以html結尾。htmlmin預設壓縮屬性當中的空白字元,但是其它的不刪除,所以指定collapseWhitespace為true,這樣壓縮掉所有的空白字元和換行符,minifyCss壓縮掉html裡css的空白,依此類推
//在dist當中又進行讀取流,又進行寫入流,容易讀寫錯亂,所以將寫入檔案的檔名改成另一個
.pipe(dest('release'))
}
module.exports = {
compile,
build,
serve,
develop,
useref
}
Gulp案例補充:
把module.exports 中匯出的三個任務都放到package.json的scripts當中,可以更容易讓別人理解一點。想要了解一個專案的構建過程的話一般會從scripts裡面著手。下面來看如何提取多個專案自動化構建的過程:
封裝工作流:
提取一個可複用的自動化工作流,通過我們去建立一個新的模組去包裝一下gulp,然後把這個自動化工作流給它包裝進去,因為gulp只是一個自動化工作流的平臺,他不負責幫你提供任何的構建任務,你的構建任務需要通過你的Gulpfile去定義,把gulpfile和gulp通過一個模組結合到一起,結合到一起過後我們在以後同型別專案當中就使用這個模組去提供自動化工作流就好了,以上就是我們的一個辦法。先做一些準備性的工作,具體的方法就是建立一個模組,然後把這個模組釋出到npm倉庫上面,最後在專案當中使用這個模組就可以了:
- 建立gulp-pages目錄結構
- 將package.json中指向的入口檔案地址,改為“lib/index.js”,如下圖所示:
- 編寫 lib/index.js 入口檔案,此入口檔案,就是上面Gulp案例中的 gulpfile.js檔案:
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() //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 serve = () => {
watch('src/assets/styles/*scss',style)//指定兩個引數,第一個引數路徑萬用字元,第二個是對應的任務
watch('src/assets/scripts/*.js',script)
watch('src/*.html',page)
watch([
'src/assets/fonts/**',
'src/assets/images/**',
'public/**'
],bs.reload)
bs.init({
notify:false,
port:3000,
files:'dist/**',
server:{
baseDir:['temp','src','public'],
routes:{
'/node_modules':'node_modules'
}
}
})
}
const clean = () => {
return del(['dist','temp'])//返回的是promise,意味著delete完成後,它可以標記clean任務執行完成
}
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:data}))
.pipe(dest('temp'))
.pipe(bs.reload({stream:true}))
}
//字型檔案編譯
const font = () =>{
return src('src/assets/fonts/**',{base:'src'})
.pipe(plugins.imagemin())
.pipe(dest('dist'))
}
//圖片壓縮編譯
const image = () =>{
return src('src/assets/images/**',{base:'src'})
.pipe(plugins.imagemin())
.pipe(dest('dist'))
}
//這是個額外拷貝的任務
const extra = () =>{
return src('public/**',{base:'public'})
.pipe(dest('dist'))
}
const useref = () => {
return src('temp/*.html',{base:'temp'})//找dist下面的html
.pipe(plugins.useref({searchPath:['temp','.']}))//
//html js css
.pipe(plugins.if(/\.js$/,plugins.uglify()))//是否以js結尾
.pipe(plugins.if(/\.css$/,plugins.cleanCss()))//是否以css結尾
.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,
}
- Gulpfile.js中的一些構建任務是依賴一些模組的,肯定是需要把這些模組作為我們ly-pages這個專案(模組)的依賴安裝,這樣的話在別的專案中去使用這個模組的時候會自動的去安裝依賴,所以需要把依賴的模組在原專案案例當中package.json對應的devDependencies中的開發依賴複製到要構建的ly-pages當中的Dependencies。原專案當中的dependencies中可以刪掉,因為不同的專案裡面的生產依賴是不一樣的,但是對於自動化構建任務中的開發依賴都是相同的 :
安裝ly-pages時,它會自動的把dependencies中的依賴安裝下來,並不會去安裝裡面的開發依賴,開發依賴指的是開發我們這個模組所需要的的東西 ,最終工作環節只會有dependencies裡面的東西,所以複製到dependencies裡面。
- 使用命令安裝所有依賴:
$ yarn # or npm i
- 根據準備工作,清空原始案例專案ly-gulp-demo當中的gulpfile.js和刪除node_modules與package.json當中的一些依賴,正常的流程是把ly-pages這個模組釋出到npm當中,然後在ly-gulp-demo裡面安裝這個模組,但是現在開發階段,ly-pages這個模組沒有完成,需要本地除錯,最簡單的方式是通過link的方式把ly-pages這個模組link到ly-gulp-demo專案的node_modules當中,開啟ly-pages的終端:
yarn link
- 將 ly-pages 模組作為 ly-gulp-demo 的依賴模組,在 ly-gulp-demo 的命令終端執行:
yarn link ly-pages # or npm link ly-pages
執行後如下圖所示:
可以看到,ly-gulp-demo 的目錄結構中,會新增一個node_modules的資料夾,其目錄結構和 ly-pages 的一模一樣。這其實是一個軟連線,當 ly-pages 中的內容改變時,ly-gulp-demo 會隨著改變.
簡單工作流:
在ly-pages中的gulpfile.js檔案中,我們會採用的是定義任務,再將任務以模組進行匯出的形式。現在,我們匯入了公共的 gulp-pages 模組,因此,可以直接匯出載入的模組。
- 在ly-gulp-demo的gulpfile.js檔案中,匯入載入模組後的命令
module.exports = require('ly-pages')
執行結果,如下圖所示:
可以看到,此時執行會報錯。因為用到的 gulp命令是從bin目錄中獲取的,而此時是不存在這個目錄的,此時可以先手動安裝 gulp,使編譯成功。
- 手動安裝 gulp 依賴,臨時解決 gulp 不識別問題
$ yarn add gulp --dev # or npm install gulp --save-dev
執行結果,如下圖所示:
可以看到,此時執行會報錯。因為在 index.js中,使用了配置資料,但是每個專案的配置資料可能都有所不同,考慮到約定大於配置,因此需要將配置資料單獨抽象出來,形成配置檔案,被 gulpfile.js 檔案引入。
- 在 gulp-demo 建立 pages.config.js 配置檔案,書寫配置資料(資料省略,可以參考上面的data資料),例如像vue-cli它在工作的時候就會讀取專案根目錄下的vue.config.js。
module.exports = {
data: {
// 要配置的data
}
}
- 改造ly-pages的入口檔案index.js,將原來的data配置資料改為以下程式碼
// 返回當前命令列所在的工作目錄
const cwd = process.cwd()
let config = {
// default config
}
// 使用 try ... catch ,防止 pages.config.js 不存在的情況
try {
const loadConfig = require(`${cwd}/pages.config.js`)
config = Object.assign({}, config, loadConfig)
} catch (e) {}
const page = () => {
return src('src/*.html', { base: 'src' })
.pipe(plugins.swig({ data: config.data })) // 此時,data屬性名和屬性值不同,不可以簡寫
.pipe(dest('temp'))
.pipe(bs.reload({ stream: true }))
}
在ly-gulp-demo中執行yarn build後出現下圖的情況:
可以看到,此時執行會報錯。這是因為 根據上面的寫法,會在 ly-gulp-demo的node_modules中查詢對應的依賴,而 ly-gulp-demo 中並沒有
- 解決找不到 ‘@babel/preset-env’ 依賴的問題,在ly-pages中的index.js檔案裡的script任務裡presets修改為require的形式獲取:
const script = () => {
return src('src/assets/scripts/*.js', { base: 'src' })
.pipe(plugins.babel({ presets: [require('@babel/preset-env')] }))
.pipe(dest('temp'))
.pipe(bs.reload({ stream: true }))
}
可以看到,採用了require 導包的方法,因為 require 會從當前檔案開始,逐級往上查詢對應的依賴包。然後yarn build 執行成功。
封裝工作流:抽象路徑配置
對於ly-pages裡面的index.js裡面寫死的路徑,這些路徑在我們使用的專案當中可以看做是一個約定,提供可配置的能力也很重要,在我們使用的專案當中,如果要求src下的目錄不叫src,此時就可以通過配置的方式去覆蓋,這樣的話更靈活一些,在config裡面的預設配置裡新增:
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() //plugins是一個物件,所有的外掛都是這個物件下面的屬性
const cwd = process.cwd()//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',
image:'assets/images/**',
fonts:'assets/fonts/**'
}
}
}
try {
const loadConfig = require(`${cwd}/pages.config.js`)
config = Object.assign({},config,loadConfig)
}catch(e) {
}
//把開發伺服器定義到單獨的一個任務當中使用
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([
config.build.paths.fonts,
config.build.paths.images,
],{cwd:config.build.src},bs.reload)
watch('**',{cwd:config.build.public},bs.reload)
bs.init({
notify:false,
port:3000,
files:'dist/**',
server:{
baseDir:[config.build.temp,config.build.src,config.build.public],
routes:{
'/node_modules':'node_modules'
}
}
})
}
const clean = () => {
return del([config.build.dist,config.build.temp])//返回的是promise,意味著delete完成後,它可以標記clean任務執行完成
}
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))
.pipe(bs.reload({stream:true}))
}
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}))
.pipe(dest(config.build.temp))
.pipe(bs.reload({stream:true}))
}
//字型檔案編譯
const font = () =>{
return src(config.build.paths.fonts,{base:config.build.src,cwd:config.build.src})
.pipe(plugins.imagemin())
.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))
.pipe(bs.reload({stream:true}))
}
//這是個額外拷貝的任務
const extra = () =>{
return src('**',{base:config.build.public,cwd:config.build.public})
.pipe(dest(config.build.dist))
}
const useref = () => {
return src(config.build.paths.pages,{base:config.build.temp,cwd:config.build.temp})//找dist下面的html
.pipe(plugins.useref({searchPath:[config.build.temp,'.']}))//
//html js css
.pipe(plugins.if(/\.js$/,plugins.uglify()))//是否以js結尾
.pipe(plugins.if(/\.css$/,plugins.cleanCss()))//是否以css結尾
.pipe(plugins.if(/\.html$/,plugins.htmlmin({
collapseWhitespace:true,
minifyCss:true,
minifyJs:true
})))
.pipe(dest(config.build.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,
}
在ly-gulp-demo中的pages.config.js檔案中新增build的配置路徑:
module.exports = {
build:{
src:'src',
dist:'release',
temp:'.tmp',//temp我們一般會放在.開頭的目錄
public:'public',
paths:{
styles:'assets/styles/*.scss',
scripts:'assets/scripts/*.js',
pages:'*.html',
image:'assets/images/**',
fonts:'assets/fonts/**'
}
},
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()
}
}
封裝工作流:包裝Gulp CLI:
因為gulpfile.js檔案比較冗餘,可以直接執行下面命令代替 gulpfile.js 檔案
$ # --cwd 設定當前執行目錄
$ yarn gulp --gulpfile ./node_modules/gulp-pages/lib/index.js --cwd
上面的命令需要傳參,不太易操作,下面採用腳手架的形式,代替上面的操作。
- 建立 cli 的入口檔案,一般存放在 專案根目錄/bin目錄中,這裡命名為 ly-pages.js。
- 在 ly-pages的專案中的package.json 檔案中,新增 cli 入口檔案的配置指向,其餘配置省略
此時,需要重新將 ly-pages 模組 link到全域性。
- 配置ly-pages.js 入口檔案,替代上述的手動輸入
#!/usr/bin/env node
process.argv.push('--cwd')
process.argv.push(process.cwd())//指定當前命令的執行工作目錄
process.argv.push('--gulpfile')
process.argv.push(require.resolve('..'))//,指向gulpfile的入口檔案,resolve是找到這個模組對應的路徑
require('gulp/bin/gulp')//整合gulp
- 在ly-gulp-demo目錄命令介面使用命令執行任務
$ ly-pages clean
$ ly-pages build
$ ly-pages develop
執行結果跟未包裝時候一致。
釋出和使用模組詳見:前端工程化概述與腳手架工具。
使用釋出後的模組:
- 新建專案資料夾,並建立public資料夾、src資料夾、pages.config.js檔案等
$ mkdir project-name
$ cd project-name
目錄結構,如下圖所示:
- 建立並初始化package.json 包管理檔案
$ yarn init --yes # or npm init -y
- 安裝釋出的ly-pages模組
$ yarn add ly-pages --dev # or npm install ly-pages --save-dev
- 執行 ly-pages 中暴露出的 任務命令
$ yarn alisone-gulp-pages clean
$ yarn alisone-gulp-pages build
$ yarn alisone-gulp-pages develop
- 在 NPM Scripts中,進行配置,即 package.json 中的 scripts 屬性,程式碼示例如下:
{
"scripts": {
"clean": "alisone-gulp-pages clean",
"build": "alisone-gulp-pages build",
"develop": "alisone-gulp-pages develop"
}
}
- 執行命令,進行操作:
$ yarn clean # or npm run clean
$ yarn build # or npm run build
$ yarn develop # or npm run develop
FIS
fis的核心特點是高度整合,它把開發過程中常見的構建任務和除錯任務都整合在了內部,開發者通過簡單的配置檔案的方式去配置我們構建過程需要完成的工作,不需要像gulp和grunt一樣定義一些任務,fis自己內建了一個配置任務,不像gulp和grunt一樣要自定義。
- 全域性安裝 或 區域性安裝 fis3
$ yarn global add fis3 # or npm install fis3 -g
- 執行 fis3 中預設的構建任務 release,會將專案中所有需要被構建的目錄,存放到一個臨時目錄中
$ fis3 release
- 指定專案的輸出目錄為 output
$ fis3 release -d output
建立 fis-conf.js 配置檔案,進行資源定位
// 利用 fis 資源定位
// match() 的第一個引數,是指選擇器
// 後面的引數,是對於匹配到的檔案的配置
fis.match('*.{js,scss,png}', {
release: '/assets/$0' // $0 指的是當前檔案的原始結構
})
- 安裝 fis-parser-node-sass 外掛模組,用來編譯 sass 檔案
$ yarn global add fis-parser-node-sass # or npm install fis-parser-node-sass -g
- 在 fis-conf.js 配置檔案中,配置 sass 的編譯並壓縮
程式碼示例如下:
fis.match('**/*.scss', {
rExt: '.css', // 修改編譯後的副檔名
parser: fis.plugin('node-sass'), // 自動載入編譯外掛
optimizer: fis.plugin('clean-css') // css的壓縮外掛,內建外掛
})
- 安裝 fis-parser-babel-6.x 外掛模組,將 ES6 語法轉換為 ES5 語法
$ yarn global add fis-parser-babel-6.x # or npm install fis-parser-babel-6.x -g
- 在 fis-conf.js 配置檔案中,配置 ES6 轉換為 ES5
fis.match('**/*.js', {
parser: fis.plugin('babel-6.x'), // 自動載入編譯外掛
optimizer: fis.plugin('uglify') // js的壓縮外掛,內建外掛
})
- 使用 fis3 inspect 命令,檢視在轉換過程中,有哪些檔案被轉換
$ fis3 inspect
以上為fis的基本介紹,更詳細的自行百度。
相關文章
- Maven 自動化構建Maven
- 自動化構建映象:Packer
- 淺談自動化構建之grunt
- 淺談自動化構建之gulp
- Webpack自動化構建實踐指南Web
- Jenkins自動化前端專案構建Jenkins前端
- 構建高效的自動化測試框架框架
- Jenkins + Gitee 實現程式碼自動化構建JenkinsGitee
- Android Gradle Groovy自動化構建進階篇AndroidGradle
- Jenkins 構建自動化 .NET Core 釋出映象Jenkins
- Element-UI 中 Make 自動化構建分析UI
- 前端自動化:Node 命令列前端自動構建釋出系統前端命令列
- Jenkins + GitHub 自動構建JenkinsGithub
- Gradle自動化專案構建之快速掌握GroovyGradle
- 利用fastlane進行專案的自動化構建AST
- 使用gulp編寫常用自動化構建任務
- Gradle自動實現Android元件化模組構建GradleAndroid元件化
- 「移動開發」iuap mobile玩轉前端自動化構建移動開發前端
- 關於自動化構建的思維導圖解析圖解
- Mac 環境下 Android 使用 Jenkins 構建自動化打包MacAndroidJenkins
- 教你如何搭建一個自動化構建的部落格
- jenkins流水線自動構建配置Jenkins
- iOS 自動化釋出 Fastlane 本地構建 IPA 並分發iOSAST
- 通過Gradle自動實現Android元件化模組構建GradleAndroid元件化
- docker-compose+ jenkins + gogs+ maven自動化構建與部署DockerJenkinsGoMaven
- 構建郵件自動化營銷的7大技巧,助你事半功倍!
- Gradle自動化專案構建之Gradle學習及實戰Gradle
- 前端之路: 用github的webhooks實現專案自動化構建前端GithubWebHook
- 自動化構建工具 Grunt
- maven自動化構建工具Maven
- Maven:自動化構建工具Maven
- K8S+Jenkins自動化構建微服務專案(後續)K8SJenkins微服務
- 使用ChatGPT自動構建知識圖譜ChatGPT
- jenkins自動構建前端專案(window,vue)Jenkins前端Vue
- iOS自動構建打包釋出指令碼iOS指令碼
- webpack自動化架構入門Web架構
- 直播預告 | 構建超自動化平臺,助力品牌電商精細化運營
- 【IDL】 自動構建泰森多邊形(Voronoi Polygon)Go