主頁 > 前端設計 > 一文讓你學會封裝自己的前端自動化構建作業流(gulp)

一文讓你學會封裝自己的前端自動化構建作業流(gulp)

2021-04-13 11:22:13 前端設計

說起前端自動化構建,相信做過前端的小伙伴們都不會陌生,可能第一感覺就會想到webpack,但是,其實webpack本質意義上應該是一個強大的模塊打包器,以入口檔案為起點,結合檔案間各種參考關系,將各種復雜的檔案最終打包成一個或多個瀏覽器可識別的檔案,所以說,webpack更大意義上是一個模塊打包器,而非自動化構建工具,今天我們來介紹的是一款強大的自動化構建工具gulp

什么是自動化構建

自動化構建簡單理解就是將源代碼轉化為生產環境代碼的程序,
它的出現,省去了我們很大一部分人工的重復性作業,一定程度的提升了我們的開發效率

常用的自動化構建工具

  • Grunt
  • Gulp
  • FIS

區別

  • grunt 和 gulp本身更像一個構建平臺,而實際完成構建需要借助各種插件來實作各個具體的構建任務,故gurnt和gulp之間其實是可以相互轉化得,即能用grunt完成得事情,用gulp也能完成,能用gulp完成的事情,用grunt同樣能完成,
  • grunt 任務的構建是基于臨時檔案完成的,也就是說,grunt去決議一個檔案時,會先讀取這個檔案,然后經過插件處理后,先寫入到一個臨時檔案中,然后另一個插件做下一步處理時,會去讀取這個臨時檔案中的內容,然后經過插件處理后,再寫入到另一個臨時檔案中,直到全部處理完成,再寫入到目標檔案中(生產代碼),故可以看出,grunt的每一步任務的構建,都會伴隨磁盤的讀寫,故其構建速度會比較慢,故現在用的人也少了
  • gulp 任務的構建是基于記憶體完成的,也就是說,gulp決議一個檔案是以檔案流的形式,先讀取檔案的檔案流,寫入到記憶體中,然后經過中間各種插件處理,最終才寫入到目標檔案中(生產代碼),故gulp一個任務的構建程序,只有第一步和最后一步是設計到磁盤讀寫的,其他中間環節都是在記憶體中完成,故其構建速度會非常快,故gulp應該是當前最主流的自動化構建工具
  • FIS 百度團隊推出的自動化構建工具,大而全,集成了很多功能,更容易上手,但現在沒怎么維護了,用的人也非常少了

初識Gulp

gulp作業原理

在這里插入圖片描述
以上圖片是對gulp作業原理很好的一個解讀,gulp主要作業原理就是將檔案讀取出來,然后中間經過一系列的處理,最終轉換成我們生產環境所需要的內容,然后寫入到目標檔案中,
而這個程序中最重要的就是gulp的管道pipe(),gulp就是利用pipe()來實作一個流程到下一個流程的過渡,詳情請看代碼

const fs = require('fs')

const stream = (done) => {
  const readStream = fs.createReadStream('package.json') // 讀取流,讀取檔案
  const writeStream = fs.createWriteStream('temp.txt') // 寫入流,寫入檔案
  const transform = new Transform({
    transform: (chunk, encoding, callback) => {
      // 這里可以對讀取的流進行各種轉換操作,具體如何轉換我就不寫了
    }
  }) // 轉換流
  return readStream // 讀取
    .pipe(transform) // 轉換
    .pipe(writeStream) // 寫入
    // return 讀取流 實際會呼叫readStream的end事件,告知結束任務
}
module.exports = {
  stream
}

如上,gulp核心作業原理就是這樣,通過pipe這樣一個管道將上一步處理完的東西傳遞給下一步進行處理,全部處理完成后,最終寫入目標檔案

gulp需要有一個gulpfile.js檔案,實作這些構建任務的代碼一般就寫在這個gulpfile.js檔案中,如以上代碼就是寫在gulpfile.js中的

但是,以上代碼我們是通過node.js原生實作的,實際讀取檔案,寫入檔案以及中間對檔案進行各種處理,gulp都給我們提供了各種插件以及方法,我們都可以直接安裝或者直接使用

gulp常用Api

const { src, dest, parallel, series, watch } = require('gulp')
  • src:創建讀取流,可直接src(‘源檔案路徑’) 來讀取檔案流
  • dest:創建寫入流,可直接dest(‘目標檔案路徑’) 來將檔案流寫入目標檔案中
  • parallel:創建一個并行的構建任務,可并行執行多個構建任務 parallel(‘任務1’,‘任務2’,‘任務3’,…)
  • series:創建一個串行的構建任務,會按照順序依次執行多個任務 series(‘任務1’,‘任務2’,‘任務3’,…)
  • watch:對檔案進行監視,當檔案發生變化時,可執行相關任務
    watch(‘src/assets/styles/*.scss’, 執行某個任務)

從0到1實作一個完整的自動化作業流

下面我們利用一個例子來從0到1實作一個完整的自動化作業流

首先,我們得準備一份開發時得源代碼
在這里插入圖片描述
代碼目錄大家可以通過腳手架去生成

目錄介紹

1、public下存放不需要經過轉換得靜態資源
2、src下存放專案源檔案
在這里插入圖片描述

3、assets下存放其他資源檔案,如,樣式檔案,腳本檔案,圖片,字體等
在這里插入圖片描述
下面,我們要利用gulp來實作一個自動化構建作業流,將這些檔案都能夠自動轉化為生產環境可用得資源檔案

目標

1、將html檔案轉化為html檔案,存放到dist下,并且處理html中得一些模板決議,以及資源檔案得引入問題(如html檔案中引入了css,js 等),并對html檔案進行壓縮處理

2、將scss檔案轉化為瀏覽器可識別得css檔案,并壓縮

3、將js檔案轉化為js檔案,并處理js代碼中一些瀏覽器無法識別得語法轉化為可識別得,如ES6.ES7轉ES5

4、將圖片進行壓縮

5、將字體進行壓縮

6、實作一個開發服務器,實作邊開發,邊構建

7、相關優化

8、封裝自動化作業流,將我們完成得gulpfile.js 封裝成一個公用模塊,便于后續其他類似專案可以直接按照這個模塊就可立即使用

開始實作

準備作業

按照gulp,并引入相關api
yarn add gulp --dev

在專案根目錄下創建gulpfile.js檔案,在檔案中引入gulp相關方法

const { src, dest, parallel, series } = require('gulp')

1、創建相關得構建任務,并測驗

創建樣式編譯任務

// 定義樣式編譯任務
const sass = require('gulp-sass') // 編譯scss檔案得

const scss = () => {
  return src('./src/assets/styles/main.scss', {base: 'src'}) // 讀取檔案
    .pipe(sass()) // sass編譯處理
    .pipe(dest('./dist')) // 寫入到dist檔案夾下
}

// 匯出相關任務
module.exports = {
  scss
}

以上src方法中第二個引數 是為了指定基礎路徑,如果不指定,打包后則會丟失路徑,直接將打包后的css檔案放在dist目錄下,
如果指定了,就會將指定的目錄后面的目錄都保留下來,即 assets/styles/main.css

運行yarn gulp scss 運行構建任務
在這里插入圖片描述

其他構建任務也都一樣創建
思路:
先建立不同型別檔案的編譯構建任務,將需要編譯的各個任務進行編譯構建,并一個個進行測驗,確保構建沒問題
當然,編譯不同檔案需要用到不同的插件,故同時需要安裝相應的插件,并引入相關插件(引入的代碼我就不貼了)

  • 編譯scss 需要gulp-scss插件 (任務scss)
  • 編譯腳本 需要gulp-babel插件,同時需要安裝@babel/core,gulp-babel的作用主要就是去呼叫@babel/core插件,
    同時為了能夠轉換ES6及以上新特性代碼,還需要安裝@babel/preset-env插件,用于轉換新特性 (任務script)
  • 編譯html 需要gulp-swig插件,用于傳入模板所需要的資料 (任務html)
  • 編譯image圖片以及font字體檔案,需要 gulp-imagemin插件,用于對圖片和字體進行壓縮 (任務image和font)
  • 建立其他不需要編譯的檔案的構建任務,不需要編譯的就直接拷貝到目標路徑中 (任務copy)
    附上以上6個任務代碼
// html模板中需要的資料
const data = {
  menus: [
    {
      name: 'Home',
      icon: 'aperture',
      link: 'index.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 scss = () => {
  return src('./src/assets/styles/*.scss', { base: 'src' })
    .pipe(sass())
    .pipe(dest('./dist'))
}

// 定義腳本編譯任務
const script = () => {
  return src('./src/assets/scripts/*.js', { base: 'src'})
    .pipe(babel({ presets: ['@babel/preset-env'] })) // 指定babel去決議ECMAScript新特性代碼
    .pipe(dest('./dist'))
}

// 定義html模板編譯任務
const html = () => {
  return src('./src/**/*.html', { base: 'src' })
    .pipe(swig({ data })) // 指定html模板中的資料
    .pipe(dest('./dist'))
}

// 定義圖片編譯任務
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 copy = () => {
  return src('./public/**', { base: 'public' })
    .pipe(dest('./dist'))
}

module.exports = { scss, script, html, image, font, copy }

然后運行yarn gulp 任務名 來運行構建任務進行測驗

這里說明下,html任務中傳入的data,因為html源檔案中用到了模板引擎,里面用到了相關資料,故我們決議時,需要傳入相關的資料
在這里插入圖片描述

2、合并任務

因以上6個任務在構建程序中戶不影響,故可以進行并行構建,故此時,我們可以利用gulp提供的parallel方法來新建一個并行任務
但在建立任務之前,我們可以把任務進行分類,前面5個為都需要進行編譯的任務,我們可以先合并為一個compile任務,然后再用這個compile任務
和copy任務并行合并為一個新的任務build

// 因以上任務都是需要編譯的任務,且作業程序互相不受影響,故可以并行執行,故將以上5個任務合并成一個并行任務
const compile = parallel(scss, script, html, image, font)

// 將需要編譯的任務和不需要進行編譯的任務合并為一個構建任務
const build = parallel(compile, copy)

下面我們測驗一下
運行 yarn gulp build
在這里插入圖片描述

在這里插入圖片描述
可以看到,相關任務,就都被打包了

3、任務初步優化

1、 每次構建時,都會把構建后的檔案寫入到dist目錄下,那么我們是不是要在每次寫入dist之前,將dist目前清空一下會比較好啊,可以防止多余無用代碼的出現
怎么做:新增del模塊,可以用于幫我們洗掉指定目錄下的檔案(yarn add del --dev)

const del = require('del')
// 定義清除目錄下的檔案任務
const clean = () => {
  return del(['dist'])
}

此時,我們需要將新增的這個clean任務加入到構建流程中,此時,我們要想,我們是不是希望在其他任務將檔案寫入dist之前去清除dist目錄下的檔案啊
那么,此時,clean任務是不是就得在其他構建任務之前去執行啊,所以此時,我們需要將原來得build任務,串行加上一個clean任務

// 合并構建任務
const build = series(clean, parallel(compile, copy))

2、我們之前安裝了很多gulp插件(gulp-開頭得插件),每次我們新安裝一個,就得引入一次,如果以后插件多了,是不是就會有很多插件得參考啊,此時我們可以借助gulp得另一個插件來解決這個問題gulp-load-plugins, 此插件會幫我們加載gulp下得所有插件,故我們只需要引入這個插件后,就可以直接通過這個插件,拿到gulp下得所有插件,下面,我們來修改一下代碼,前面插件得引入,我們就不需要了

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

  // 定義樣式編譯任務
  const scss = () => {
    return src('./src/assets/styles/*.scss', { base: 'src' })
      .pipe(plugins.sass())
      .pipe(dest('./dist'))
  }

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

  // 定義html模板編譯任務
  const html = () => {
    return src('./src/**/*.html', { base: 'src' })
      .pipe(plugins.swig({ data }))
      .pipe(dest('./dist'))
  }

  // 定義圖片編譯任務
  const image = () => {
    return src('./src/assets/images/**', { base: 'src' })
      .pipe(plugins.imagemin())
      .pipe(dest('./dist'))
  }

  // 定義字體編譯任務
  const font = () => {
    return src('./src/assets/fonts/**', { base: 'src' })
      .pipe(plugins.imagemin())
      .pipe(dest('./dist'))
  }

4、起一個開發服務器

下面,我們開始起一個開發服務器,完成開發時邊開發邊構建的功能
起一個開發服務器需要用到插件browser-sync
安裝browser-sync插件 yarn add browser-sync
引入插,并創建一個開發服務器

const browserSync = require('browser-sync')
// 創建一個開發服務器
const bs = browserSync.create()

const serve = () => {
  bs.init({
    notify: false, // 關閉頁面打開時browser-sync的頁面提示
    port: 2080, // 設定埠
    server: {
      baseDir: 'dist', // 設定開發服務器的根目錄,會取此目錄下的檔案運行
      routes: {
        '/node_modules': 'node_modules' // 解決dist后的檔案直接引入node_modules下檔案的問題
      }
    }
  })
}

上面說一下routes選項
主要是指定打包后,html檔案中直接引入的node_modules下的包檔案的問題,告知開發服務器直接去根目錄下的node_modules檔案夾下面找對應的檔案
在這里插入圖片描述

此時,我們開發服務器已經起了一個了,并告知了服務器去取dist下的檔案作為運行檔案,但是此時,還會有問題,那就是,如果dist下的檔案發生了變化后,我們的開發服務器是無法得知的,此時我們需要配置一個files屬性,來對dist下的檔案進行監視,

const serve = () => {
  bs.init({
    notify: false, // 關閉頁面打開時browser-sync的頁面提示
    port: 2080, // 設定埠
    files: 'dist/**', // 監聽dist下所有檔案
    server: {
      baseDir: 'dist', // 設定開發服務器的根目錄,會取此目錄下的檔案運行
      routes: {
        '/node_modules': 'node_modules' // 解決dist后的檔案直接引入node_modules下檔案的問題
      }
    }
  })
}

此時,我們已經可以監聽dist下的檔案了,

5、開發服務器優化

雖然我們現在能對dist下的檔案進行監視了,但是,依然是無法實作開發程序中,頁面能即時回應的目的的,因為我們開發程序中修改的是源代碼,而不是dist下的代碼,那如何實作呢,繼續往下看

5.1 監聽構建前的源檔案,保證開發程序中能夠實作修改代碼后,頁面立刻得到相應
實作方式:利用gulp自帶的watch模塊對src下的源檔案進行監聽,源檔案發生變化時,重新執行對應的構建任務,那么會重新構建,構建后,dist下的檔案就會發生變化,serve通過files屬性就能監聽到

const serve = () => {
  // watch監聽相關源檔案
  watch('src/assets/styles/*.scss', scss)
  watch('src/assets/scripts/*.js', script)
  watch('src/*.html', html)
  watch('src/assets/images/**', image)
  watch('src/assets/fonts/**', font)
  watch('public/**', copy)

  bs.init({
    notify: false,
    port: 2080,
    files: 'dist/**',
    server: {
      baseDir: 'dist',
      routes: {
        '/node_modules': 'node_modules'
      }
    }
  })
}

5.2 進一步優化,上面我們已經實作了開發程序中,修改檔案頁面能即時回應,但是我們上面6個watch監聽了6類檔案,每類檔案發生變化后,我們都重新執行了對應的構建任務,
我們試想,在開發程序中,我們只需要當檔案發生變化時,頁面能即時回應就行了,像html,scss,js等檔案,需要編譯成瀏覽器可識別的檔案我們才能看到頁面發生變化,故每次這類檔案發生變化時,我們都去啟動對應的任務重新構建一次這無可厚非,但是,像圖片,字體以及不需要編譯的靜態檔案,我們只需要看到變化就行了,有必要呼叫對應構建任務嗎,像圖片,字體,都是對它們進行了壓縮,但我們實際開發階段,這個完全沒必要,
故,我們對這類開發階段不需要處理的檔案做個特殊處理,

5.2.1 我們在監聽圖片,字體,和public下的靜態檔案時,不再啟動對應的構建任務,而是直接呼叫browserSync的reload()方法去重新加載頁面
那么此時,我們開發服務器要拿到這些檔案是不是就不能在dist下拿了啊,因為我們沒有重新構建,故dist下不會有改變后的檔案,
此時,我們修改baseDir的根目錄為一個陣列[‘dist’, ‘src’, ‘public’],那么,服務器會優先去dist下找檔案,如果找不到,會依次去src和public目錄下尋找,像圖片,字體,以及相關靜態檔案,開發服務器是不是就會去src和public下去加載啊

const serve = () => {
  // watch監聽相關源檔案
  watch('src/assets/styles/*.scss', scss)
  watch('src/assets/scripts/*.js', script)
  watch('src/**/*.html', html)
  // watch('src/assets/images/**', image)
  // watch('src/assets/fonts/**', font)
  // watch('public/**', copy)
  watch(
    [
      'src/assets/images/**',
      'src/assets/fonts/**',
      'public/**'
    ],
    bs.reload
  )

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

5.3 有一個容易忽略的問題,我們上面serve服務器是以dist下的檔案為跟目錄,也就是服務器啟動,會默認去取dist目錄下的檔案,如果找不到,就會去取src和public下的檔案,那如果重來沒有執行過build命令,那么dist下是不是空的啊,這么一來,像樣式檔案,js檔案,html檔案,他都會取src下面找,那找到的檔案能運行嗎,是不是不能啊,所以,我們需要新建一個develop任務,此任務在啟動serve前,先執行一次compile任務,

// 因以上任務都是需要編譯的任務,且作業程序互相不受影響,故可以并行執行,故將以上5個任務合并成一個并行任務
const compile = parallel(scss, script, html)

// 合并構建任務
const build = series(clean, parallel(compile, copy, image, font))

// 開發構建任務
const develop = series(compile, serve)

我們新建了一個develop任務,讓起串行先執行compile和serve
同時,我們修改了一下compile任務,將image和font任務放入到build中了,這樣我們develop中便不需要執行這兩個任務了

5.4 上面我們說過,serve服務器是通過files屬性去監聽dist目錄下的檔案變化來實作即時更新的,可是像上面的圖片,字體以及靜態檔案,我們好像并沒有用到這個files屬性,也實作了瀏覽器的實時更新吧,那我們其他檔案,是不是也可以這樣呢,對的,也可以這樣,具體用法,見下面代碼

// 定義樣式編譯任務
const scss = () => {
  return src('./src/assets/styles/*.scss', { base: 'src' })
    .pipe(plugins.sass({ outputStyle: 'expanded' }))
    .pipe(dest('./dist'))
    .pipe(bs.reload({ stream: true })) // 構建任務每次執行后,都reload一次
}

// 定義腳本編譯任務
const script = () => {
  return src('./src/assets/scripts/*.js', { base: 'src'})
    .pipe(plugins.babel({ presets: ['@babel/preset-env'] }))
    .pipe(dest('./dist'))
    .pipe(bs.reload({ stream: true })) // 構建任務每次執行后,都reload一次
}

// 定義html模板編譯任務
const html = () => {
  return src('./src/**/*.html', { base: 'src' })
    .pipe(plugins.swig({ data }))
    .pipe(dest('./dist'))
    .pipe(bs.reload({ stream: true })) // 構建任務每次執行后,都reload一次
}

以上添加了一個stream:true,意思是重新加載不需要進行讀寫操作,而是直接以流的方式往瀏覽器中推

好了,開發服務器的優化就到這了
下面,我們繼續來優化生產環境的構建build

6、build的構建任務的優化

6.1 上面我們說過,serve服務器中配置了一個routes,是因為構建后的html檔案引入了一些外部的資源檔案,我們去處理那些資源檔案了,

但是,build環境中,這些檔案可能就找不到了,因為dist下沒有node_modules檔案夾,那么我們構建的時候該如何去處理這種構建后的資源參考問題呢
首先,我們可以看下構建后的html
在這里插入圖片描述
可以看出,這種資源檔案,構建后,會生成對應的build注釋,標識了后續可將兩個注釋中間的部分合并成為一個新的檔案(vendor.css),那么如何處理這種情況呢,
gulp提供了一種叫useref的插件來處理這種情況,他會將注釋中間參考的資源合并成為一個新的資源檔案
安裝 gulp-useref (yarn add gulp-useref --dev)
新建任務用此插件去處理這種情況

const useref = () => {
  return src('dist/*html', { base: 'dist' }) // 讀取的是構建后的檔案,故是dist下
    .pipe(plugins.useref({ searchPath: ['dist', '.']})) // 請求的資源路徑去哪找
    .pipe(dest('dist'))
}

上面的searchPath 是指定構建時,請求的資源檔案去什么地方找,如上圖中的main.css,我們可以直接在dist下找,如果找不到,那么我們去當前根目錄下找,故配置了第二個 ‘.’ 這個 . 就代表當前根目錄 ,比如上面的bootstrap.css 就會去根目錄下找,找到后,直接將引入的這個css打包進dist下,并合并成vendor.css ,
這個合并,可能你們不大理解,看下圖 ,你們就理解了
在這里插入圖片描述
這個注釋中間引入了3個檔案,那么都會被打包成vendor.js一個檔案,同時會將注釋洗掉

此時,其實還會有點問題,大家可以看到讀取檔案是從dist下去讀取,寫入檔案又是寫入到dist下面,這其實會產生沖突,從同一個地方又讀又寫,是不是有問題啊,
此時,我們可以通過一個中間檔案來進行一個過度,如何過度,請看6.2

6.2 我們可以在構建的時候,可以先讓他構建到一個中間目錄中,比如temp,然后useref再去temp中去讀檔案,讀取后,再通過useref插件進行處理,然后再寫入到dist中,那么我們原來的構建任務的寫入路徑就都要改了,但是這個只針對html,style,js 因為useref是處理引入的html以及js,css等資源路徑的

// 定義樣式編譯任務
const scss = () => {
  return src('./src/assets/styles/*.scss', { base: 'src' })
    .pipe(plugins.sass({ outputStyle: 'expanded' }))
    .pipe(dest('./temp')) // 改成temp
    .pipe(bs.reload({ stream: true })) // 構建任務每次執行后,都reload一次
}

// 定義腳本編譯任務
const script = () => {
  return src('./src/assets/scripts/*.js', { base: 'src'})
    .pipe(plugins.babel({ presets: ['@babel/preset-env'] }))
    .pipe(dest('./temp')) // 改成temp
    .pipe(bs.reload({ stream: true })) // 構建任務每次執行后,都reload一次
}

// 定義html模板編譯任務
const html = () => {
  return src('./src/**/*.html', { base: 'src' })
    .pipe(plugins.swig({ data }))
    .pipe(dest('./temp')) // 改成temp
    .pipe(bs.reload({ stream: true })) // 構建任務每次執行后,都reload一次
}

const useref = () => {
  return src('temp/*html', { base: 'temp' }) // 改成從temp下去讀取檔案流
    .pipe(plugins.useref({ searchPath: ['dist', '.']})) // 改成從temp下去讀取檔案流
    .pipe(dest('dist')) // 寫入到dist
}

// 定義清除目錄下的檔案任務
const clean = () => {
  return del(['dist', 'temp']) // 添加清除temp
}

然后修改構建流程,將useref放到compile之后再執行,同時,我們構建完以后,是不是還要將temp目錄給清除啊,因為他只是個臨時目錄

// 清除temp
const cleanTemp = () => {
  return del('temp')
}

// 合并構建任務
const build = series(clean, parallel(series(compile, useref, cleanTemp), copy, image, font))

6.3 檔案壓縮
前面我們利用useref構建的html,css,js等是不是還沒有給他進行壓縮處理啊,我們build任務一般是打包線上代碼,那么這些檔案肯定都是要進行壓縮的,那么如何壓縮呢
當然是針對不同的檔案利用不同的插件進行壓縮了
html 使用插件gulp-htmlmin yarn add gulp-htmlmin --dev
js 使用插件gulp-uglify yarn add gulp-uglify --dev
css 使用插件cleanCss yarn add gulp-clean-css --dev
同時,我們知道useref任務中是一個讀取流可能讀取到不同型別的檔案(html或css或js),因此,我們還需要一個gulp-if插件來做判斷

const useref = () => {
  return src('temp/*html', { base: 'temp' }) // 讀取的是構建后的檔案,故是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, // 壓縮html
      minifyCss: true, // 壓縮html檔案中的內嵌樣式
      minifyJs: true // 壓縮html檔案中內嵌的js
    })))
    .pipe(dest('dist'))
}

6.4 匯出相關指令
上面我們一般都是只暴露了develop 和 build兩個任務,但一般還有個clean任務,我們也是比較常用的,我們將這個任務也單獨匯出

// 匯出相關任務
module.exports = {
  clean,
  build,
  develop
}

匯出后,我們可以在package.json檔案中去配置相關指令,以便我們更方便去執行我們的命令

"scripts": {
  "clean": "gulp clean",
  "build": "gulp build",
  "develop": "gulp develop"
}

此時,我們可以直接通過yarn build去進行專案構建了

整個構建流程基本已經完成了,
下面我們來附上gulpfile.js完整代碼

// 實作這個專案的構建任務

// 引入相關依賴

const { src, dest, parallel, series, watch } = require('gulp')
const del = require('del')
const browserSync = require('browser-sync')

const loadPlugins = require('gulp-load-plugins')
const plugins = loadPlugins()
// 創建一個開發服務器
const bs = browserSync.create()
// const sass = require('gulp-sass')
// const babel = require('gulp-babel')
// const swig = require('gulp-swig')
// const imagemin = require('gulp-imagemin')

// 定義html模板需要得資料
const data = {
  menus: [
    {
      name: 'Home',
      icon: 'aperture',
      link: 'index.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 scss = () => {
  return src('./src/assets/styles/*.scss', { base: 'src' })
    .pipe(plugins.sass({ outputStyle: 'expanded' }))
    .pipe(dest('./temp'))
    .pipe(bs.reload({ stream: true })) // 構建任務每次執行后,都reload一次
}

// 定義腳本編譯任務
const script = () => {
  return src('./src/assets/scripts/*.js', { base: 'src'})
    .pipe(plugins.babel({ presets: ['@babel/preset-env'] }))
    .pipe(dest('./temp'))
    .pipe(bs.reload({ stream: true })) // 構建任務每次執行后,都reload一次
}

// 定義html模板編譯任務
const html = () => {
  return src('./src/**/*.html', { base: 'src' })
    .pipe(plugins.swig({ data }))
    .pipe(dest('./temp'))
    .pipe(bs.reload({ stream: true })) // 構建任務每次執行后,都reload一次
}

// 定義圖片編譯任務
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 copy = () => {
  return src('./public/**', { base: 'public' })
    .pipe(dest('./dist'))
}

// 定義清除目錄下的檔案任務
const clean = () => {
  return del(['dist', 'temp'])
}

// 清除temp
const cleanTemp = () => {
  return del('temp')
}

// 初始化開發服務器
const serve = () => {
  // watch監聽相關源檔案
  watch('src/assets/styles/*.scss', scss)
  watch('src/assets/scripts/*.js', script)
  watch('src/**/*.html', html)
  // watch('src/assets/images/**', image)
  // watch('src/assets/fonts/**', font)
  // watch('public/**', copy)
  watch(
    [
      'src/assets/images/**',
      'src/assets/fonts/**',
      'public/**'
    ],
    bs.reload
  )

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

const useref = () => {
  return src('temp/*html', { base: 'temp' }) // 讀取的是構建后的檔案,故是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, // 壓縮html
      minifyCss: true, // 壓縮html檔案中的內嵌樣式
      minifyJs: true // 壓縮html檔案中內嵌的js
    })))
    .pipe(dest('dist'))
}

// 因以上任務都是需要編譯的任務,且作業程序互相不受影響,故可以并行執行,故將以上5個任務合并成一個并行任務
const compile = parallel(scss, script, html)

// 合并構建任務
const build = series(clean, parallel(series(compile, useref, cleanTemp), copy, image, font))

// 開發構建任務
const develop = series(compile, serve)

// 匯出相關任務
module.exports = {
  clean,
  build,
  develop
}

但是,此時,我們發現沒有,我們寫了這么多,只是用于處理了這一個專案的構建任務,但是我們肯定是希望我們所寫的這些東西,能夠作為和當前專案結構相似的一類專案的自動化構建工具,那么,最好的辦法是不是將這個gulpfile.js封裝成一個模塊,然后發布到npm上面去啊,
那么以后人家需要使用的時候,是不是可以直接通過按照這個模塊,就立馬可以進行專案構建了啊,下面我們就來封裝一下這個作業流

自動化構建作業流封裝

1、首先,我們需要新建一個node_modules包,包名我們定為cgp-build
我這里使用了一個腳手架工具(caz)生成node_modules包的一些基礎目錄

我們先全域安裝這個腳手架
yarn global add caz

運行caz nm cgp-build生成我們的包的基本目錄
在這里插入圖片描述
這個包中,lib下的index.js就是我們這個包的入口檔案(一般包的入口檔案都是lib下的index.js檔案,而cli指令檔案的入口檔案一般是bin下的cli.js或者index.js)

也就是說,我們原來寫在gulpfile.js中的代碼,現在要放到lib/index.js中來,這里當別人執行這個包時,才會執行到這些具體的構建代碼

2、將gulpfile.js中的代碼拷貝到index.js中來

此時,gulpfile.js這個依賴了很多插件,所以這些插件都會被作為我們封裝得這個包得生產依賴,故我們需要把之前那個打包專案中得package.json中devDependencies都拷貝到我們這個包目錄中得package.json檔案中的dependencies中
在這里插入圖片描述

那么此時,后面有專案安裝了我們這個cgp-build的時,就會自動安裝這個包所依賴的這些插件,

3、提取專案中的資料
此時,還有問題,我們往上去看gulpfile.js中的代碼,發現在決議html時,是不是傳入了一個data資料啊,而data我們是直接定義在gulpfile.js中的,但是我們都知道,這個data資料,是不是專案的資料啊,不同的專案可能這個資料就不一樣了,可能有的專案html檔案中還沒有這種模板資料,所以說,這個data,是不是應該提到專案中去啊,那么提到哪呢,
我們知道,很多專案中 是不是都有config.js檔案啊,比如vue的vue.config.js,那么我們是不是也可以定義一個config檔案啊,比如就叫page.config.js,那么用我們這個cgp-build進行自動化構建的專案都需要創建一個page.config.js檔案,那我們是不是可以把這個data放到config檔案中,當作配置資料傳入啊,
而此時,我們lib/index.js檔案中,我們就可以通過引入這個config.js檔案中的配置,然后在構建的時候再使用這個配置資料

那么: 如何拿到專案目錄下的config.js檔案呢
我們分析下:我們這個包,最終是會被安裝在專案目錄的node_modules檔案夾下的
那么我們這個包中的lib/index 相當于是在專案目錄(我們假設專案目錄是page-demo)下的node_models/cgp-build/lib/index.js ,
那么我們不是拿到了專案的根目錄,就能拿到專案中的page.config.js檔案啊,
node,js提供了一個全域api process.cwd() 可以獲取到當前專案根目錄

// 獲取根目錄
const cwd = process.cwd()
let config = {} // 定義組態檔,這里面可能會有些默認配置

try {
  const loadConfig = require(`${cwd}/page.config.js`) // 獲取專案目錄中的組態檔
  config = Object.assign({}, config, loadConfig) // 合并config和loadConfig
} catch (err) {
  throw err
}

// 定義html模板編譯任務
const html = () => {
  return src('./src/**/*.html', { base: 'src' })
    .pipe(plugins.swig({ data: config.data })) // 修改為config中的data
    .pipe(dest('./temp'))
    .pipe(bs.reload({ stream: true })) // 構建任務每次執行后,都reload一次
}

那么,page.config.js的組態檔,格式如下
在這里插入圖片描述

現在,資料問題解決了,但是很多檔案的路徑我們是不是還是寫死的啊
在這里插入圖片描述
像這種路徑,不同專案,是不是可能不一樣啊,所以我們這樣寫死,也是不合理的,也應該抽象到page.config.js的配置中去

4、抽象路徑
我們先在/lib/index.js中寫入一份默認配置,當專案中配置了相關配置后,會覆寫index.js中的默認配置

// 獲取根目錄
const cwd = process.cwd()
let config = {
  build: {
    src: 'src',
    dist: 'dist',
    temp: 'temp',
    public: 'public',
    paths: {
      styles: 'assets/styles/*.scss',
      scripts: 'assets/styles/*.js',
      pages: '*.html',
      images: 'assets/images/**',
      fonts: 'assets/fonts/**'
    }
  }
} // 定義組態檔,這里面可能會有些默認配置

然后將index.js中的路徑都用config變數去代替

// 定義樣式編譯任務
const scss = () => {
  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 })) // 構建任務每次執行后,都reload一次
}

// 定義腳本編譯任務
const script = () => {
  return src(config.build.paths.scripts, { base: config.build.src, cwd: config.build.src })
    .pipe(plugins.babel({ presets: ['@babel/preset-env'] }))
    .pipe(dest(config.build.temp))
    .pipe(bs.reload({ stream: true })) // 構建任務每次執行后,都reload一次
}

// 定義html模板編譯任務
const html = () => {
  return src(config.build.paths.pages, { base: config.build.src, cwd: config.build.src })
    .pipe(plugins.swig({ data: config.data })) // 修改為config中的data
    .pipe(dest(config.build.temp))
    .pipe(bs.reload({ stream: true })) // 構建任務每次執行后,都reload一次
}

// 定義圖片編譯任務
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 copy = () => {
  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])
}

// 清除temp
const cleanTemp = () => {
  return del(config.build.temp)
}

// 初始化開發服務器
const serve = () => {
  // watch監聽相關源檔案
  watch(config.build.paths.styles, {cwd: config.build.src}, scss)
  watch(config.build.paths.scripts, {cwd: config.build.src}, script)
  watch(config.build.paths.pages, {cwd: config.build.src}, html)
  // watch('src/assets/images/**', image)
  // watch('src/assets/fonts/**', font)
  // watch('public/**', copy)
  watch(
    [
      config.build.paths.images,
      config.build.paths.images,
      `${config.build.public}/**`
    ],
    bs.reload
  )

  bs.init({
    notify: false,
    port: 2080,
    // files: 'dist/**',
    server: {
      baseDir: [config.build.dist, 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 }) // 讀取的是構建后的檔案,故是dist下
    .pipe(plugins.useref({ searchPath: [config.build.temp, '.']})) // 請求的資源路徑去哪找
    .pipe(plugins.if(/\.js$/, plugins.uglify()))
    .pipe(plugins.if(/\.css$/, plugins.cleanCss()))
    .pipe(plugins.if(/\.html$/, plugins.htmlmin({
      collapseWhitespace: true, // 壓縮html
      minifyCss: true, // 壓縮html檔案中的內嵌樣式
      minifyJs: true // 壓縮html檔案中內嵌的js
    })))
    .pipe(dest(config.build.dist))
}

我們在上面很多地方加了個cwd選項,是因為我們抽象出來的路徑,去掉了src,所以我們需要通過cwd去指定去哪個目錄下找這個路徑
在這里插入圖片描述

5、包裝gulp-cli
下面我們要包裝一下我們自己的cli命令,為什么要包裝呢,因為gulp構建時,默認是找gulpfile.js檔案的,而我們現在是放在/bin/index.js中,對于專案而言,這個檔案在/node_modules/cgp-build/lib/index.js中,所以在專案中運行yarn gulp build是會報錯的,報錯,找不到gulpfile.js檔案
在這里插入圖片描述
此時,我們需要手動去指定gulpfile.js檔案為哪個檔案

yarn gulp build --gulpfile ./node_modules/cgp-build/lib/index.js --cwd .

–cwd . 的意思是以當前專案目錄作為根目錄,因為gulp會默認以gulpfile.js檔案所在目錄為根目錄,所以我們需要特別指定一下根目錄

那么這么弄,是不是很繁瑣啊,每次我需要執行下構建任務時,都要輸入這么一大串,此時,我們就可以自定義這個包檔案自己的cli指令,將這些 --gulpfile --cwd等引數都集成到指令中去

如何定義cli
在包檔案目錄下新建bin檔案夾,并在bin中新建cli.js,然后在package.json檔案中添加bin欄位
在這里插入圖片描述
在這里插入圖片描述
cli.js檔案需要加個檔案頭 #!/usr/bin/env node(cli入口檔案都需要的)
在這里插入圖片描述

這里我解釋一下:一般包的cli指令檔案都是在包目錄下的bin目錄下,比如webpack,當你運行webpack main.js命令去打包main.js時,也是會先去找node_modules/webpack/bin/*.js檔案的

那么,此時,我們cli.js 檔案中需要寫什么呢,我們分析下
大家想啊,我們本質是要去執行gulp build --gulpfile …這種命令,
只是我們先去執行了我們自己的cli命令 cgp-build,那cgp-build執行后,去找了/bin/cli.js檔案后,我們是不是只需要在這里去執行gulp的構建命令就可以了啊,那執行gulp的構建命令本質上是不是去執行gulp/bin/**.js檔案啊,所以此時,我們只需要在我們的cli.js檔案中去運行gulp/bin/gulp.js檔案就行了
在這里插入圖片描述

這樣一來,當我們執行cgp-build build時,實際上就會執行gulp build命令

但這樣還不夠啊,我們前面是不是說了啊,我們需要攜帶引數去查找gulpfile.js檔案以及指定根目錄啊,此時,我們可以借助全域方法process.argv 這個可以拿到的其實就是引數串列,是個陣列,如:–gulpfile /node_modules/cgp-build/lib/index.js 陣列中就是[’–gulpfile’, ‘/node_modules/cgp-build/lib/index.js’]

那么,我們可以通過push方法往引數中添加引數
在這里插入圖片描述

此時,整個cli的封裝就完成了,

npm提交

npm提交我們上篇文章已經說過了,這里就隨便提一下了,
1、將包上次至開源庫,如github
2、npm publish 或者 yarn publish上傳至npm庫中

提交完后,我們測驗下
先在本地準備一個專案目錄gulp-demo,里面放入我們之前那個專案
然后安裝我們提交至npm的包 cgp-build
yarn add cgp-build --dev
在這里插入圖片描述
然后運行yarn cgp-build build 或者 cgp-build build
在這里插入圖片描述

可以看出,是沒有問題的,正常打包成功

好了,自動化構建就寫到這了,喜歡請點個贊,謝謝

轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/275498.html

標籤:其他

上一篇:JavaScript逐點突破系列之this是什么?了解完這7點很多疑惑都解決

下一篇:VR開發之使用VRTK實作基本的漫游和手柄射線功能

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • vue移動端上拉加載

    可能做得過于簡單或者比較low,請各位大佬留情,一起探討技術 ......

    uj5u.com 2020-09-10 04:38:07 more
  • 優美網站首頁,頂部多層導航

    一個個人用的瀏覽器首頁,可以把一下常用的網站放在這里,平常打開會比較方便。 第一步,HTML代碼 <script src=https://www.cnblogs.com/szharf/p/"js/jquery-3.4.1.min.js"></script> <div id="navigate"> <ul> <li class="labels labels_1"> ......

    uj5u.com 2020-09-10 04:38:47 more
  • 頁面為要加<!DOCTYPE html>

    最近因為寫一個js函式,需要用到$(window).height(); 由于手寫demo的時候,過于自信,其實對前端方面的認識也不夠體系,用文本檔案直接敲出來的html代碼,第一行沒有加上<!DOCTYPE html> 導致了$(window).height();的結果直接是整個document的高 ......

    uj5u.com 2020-09-10 04:38:52 more
  • WordPress網站程式手動升級要做好資料備份

    WordPress博客網站程式在進行升級前,必須要做好網站資料的備份,這個問題良家佐言是遇見過的;在剛開始接觸WordPress博客程式的時候,因為升級問題和博客網站的修改的一些嘗試,良家佐言是吃盡了苦頭。因為購買的是西部數碼的空間和域名,每當佐言把自己的WordPress博客網站搞到一塌糊涂的時候 ......

    uj5u.com 2020-09-10 04:39:30 more
  • WordPress程式不能升級為5.4.2版本的原因

    WordPress是一款個人博客系統,受到英文博客愛好者和中文博客愛好者的追捧,并逐步演化成一款內容管理系統軟體;它是使用PHP語言和MySQL資料庫開發的,用戶可以在支持PHP和MySQL資料庫的服務器上使用自己的博客。每一次WordPress程式的更新,就會牽動無數WordPress愛好者的心, ......

    uj5u.com 2020-09-10 04:39:49 more
  • 使用CSS3的偽元素進行首字母下沉和首行改變樣式

    網頁中常見的一種效果,首字改變樣式或者首行改變樣式,效果如下圖。 代碼: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, ......

    uj5u.com 2020-09-10 04:40:09 more
  • 關于a標簽的講解

    什么是a標簽? <a> 標簽定義超鏈接,用于從一個頁面鏈接到另一個頁面。 <a> 元素最重要的屬性是 href 屬性,它指定鏈接的目標。 a標簽的語法格式:<a href=https://www.cnblogs.com/summerxbc/p/"指定要跳轉的目標界面的鏈接">需要展示給用戶看見的內容</a> a標簽 在所有瀏覽器中,鏈接的默認外觀如下: 未被訪問的鏈接帶 ......

    uj5u.com 2020-09-10 04:40:11 more
  • 前端輪播圖

    在需要輪播的頁面是引入swiper.min.js和swiper.min.css swiper.min.js地址: 鏈接:https://pan.baidu.com/s/15Uh516YHa4CV3X-RyjEIWw 提取碼:4aks swiper.min.css地址 鏈接:https://pan.b ......

    uj5u.com 2020-09-10 04:40:13 more
  • 如何設定html中的背景圖片(全屏顯示,且不拉伸)

    1 <style>2 body{background-image:url(https://uploadbeta.com/api/pictures/random/?key=BingEverydayWallpaperPicture); 3 background-size:cover;background ......

    uj5u.com 2020-09-10 04:40:16 more
  • Java學習——HTML詳解(上)

    HTML詳解 初識HTML Hyper Text Markup Language(超文本標記語言) 1 <!--DOCTYPE:告訴瀏覽器我們要使用什么規范--> 2 <!DOCTYPE html> 3 <html lang="en"> 4 <head> 5 <!--meta 描述性的標簽,描述一些 ......

    uj5u.com 2020-09-10 04:40:33 more
最新发布
  • 我的第一個NPM包:panghu-planebattle-esm(胖虎飛機大戰)使用說明

    好家伙,我的包終于開發完啦 歡迎使用胖虎的飛機大戰包!! 為你的主頁添加色彩 這是一個有趣的網頁小游戲包,使用canvas和js開發 使用ES6模塊化開發 效果圖如下: (覺得圖片太sb的可以自己改) 代碼已開源!! Git: https://gitee.com/tang-and-han-dynas ......

    uj5u.com 2023-04-20 07:59:23 more
  • 生產事故-走近科學之消失的JWT

    入職多年,面對生產環境,盡管都是小心翼翼,慎之又慎,還是難免捅出簍子。輕則滿頭大汗,面紅耳赤。重則系統停擺,損失資金。每一個生產事故的背后,都是寶貴的經驗和教訓,都是專案成員的血淚史。為了更好地防范和遏制今后的各類事故,特開此專題,長期更新和記錄大大小小的各類事故。有些是親身經歷,有些是經人耳傳口授 ......

    uj5u.com 2023-04-18 07:55:04 more
  • 記錄--Canvas實作打飛字游戲

    這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 打開游戲界面,看到一個畫面簡潔、卻又富有挑戰性的游戲。螢屏上,有一個白色的矩形框,里面不斷下落著各種單詞,而我需要迅速地輸入這些單詞。如果我輸入的單詞與螢屏上的單詞匹配,那么我就可以獲得得分;如果我輸入的單詞錯誤或者時間過長,那么我就會輸 ......

    uj5u.com 2023-04-04 08:35:30 more
  • 了解 HTTP 看這一篇就夠

    在學習網路之前,了解它的歷史能夠幫助我們明白為何它會發展為如今這個樣子,引發探究網路的興趣。下面的這張圖片就展示了“互聯網”誕生至今的發展歷程。 ......

    uj5u.com 2023-03-16 11:00:15 more
  • 藍牙-低功耗中心設備

    //11.開啟藍牙配接器 openBluetoothAdapter //21.開始搜索藍牙設備 startBluetoothDevicesDiscovery //31.開啟監聽搜索藍牙設備 onBluetoothDeviceFound //30.停止監聽搜索藍牙設備 offBluetoothDevi ......

    uj5u.com 2023-03-15 09:06:45 more
  • canvas畫板(滑鼠和觸摸)

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>canves</title> <style> #canvas { cursor:url(../images/pen.png),crosshair; } #canvasdiv{ bo ......

    uj5u.com 2023-02-15 08:56:31 more
  • 手機端H5 實作自定義拍照界面

    手機端 H5 實作自定義拍照界面也可以使用 MediaDevices API 和 <video> 標簽來實作,和在桌面端做法基本一致。 首先,使用 MediaDevices.getUserMedia() 方法獲取攝像頭媒體流,并將其傳遞給 <video> 標簽進行渲染。 接著,使用 HTML 的 < ......

    uj5u.com 2023-01-12 07:58:22 more
  • 記錄--短視頻滑動播放在 H5 下的實作

    這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 短視頻已經無數不在了,但是主體還是使用 app 來承載的。本文講述 H5 如何實作 app 的視頻滑動體驗。 無聲勝有聲,一圖頂百辯,且看下圖: 網址鏈接(需在微信或者手Q中瀏覽) 從上圖可以看到,我們主要實作的功能也是本文要講解的有: ......

    uj5u.com 2023-01-04 07:29:05 more
  • 一文讀懂 HTTP/1 HTTP/2 HTTP/3

    從 1989 年萬維網(www)誕生,HTTP(HyperText Transfer Protocol)經歷了眾多版本迭代,WebSocket 也在期間萌芽。1991 年 HTTP0.9 被發明。1996 年出現了 HTTP1.0。2015 年 HTTP2 正式發布。2020 年 HTTP3 或能正... ......

    uj5u.com 2022-12-24 06:56:02 more
  • 【HTML基礎篇002】HTML之form表單超詳解

    ??一、form表單是什么

    ??二、form表單的屬性

    ??三、input中的各種Type屬性值

    ??四、標簽 ......

    uj5u.com 2022-12-18 07:17:06 more