學習資料:拉勾課程《大前端高薪訓練營》
閱讀建議:搭配文章的側邊欄目錄進行食用,體驗會更佳哦,
內容說明:本文不做知識點的搬運工,技術詳情請查看官方檔案,
接上一篇:前端工程化之自動化創建
自動化構建 并不是一個句子,有必要在行文前先說明一下它在本文中的賓語是前端工程,
一:認識自動化構建
隨著前端需求和專案的日益復雜,出于提高開發效率、用戶體驗以及其它工程上的需要,我們通常會借助很多更高階的語法或者工具來幫助我們更快更好的開發、部署一個前端工程,
那么如何理解構建以及自動化構建呢?個人觀點,簡單務實點說,構建就是指專案從源代碼到一個能按需運行的工程(開發環境、生產環境)所需要做的所有事情,通常來說,專案多次構建的每次構建程序都包含很多任務并且基本一致,按照任何簡單機械的重復勞動都應該讓機器去完成的思想,我們應該自動化去完成工程的構建,提高構建效率,
那么如何實作自動化構建呢?個人理解,前端工程構建其實就是一個任務流,完成了這個任務流中的所有任務即完成了構建,說到任務以及任務流,我們有必要先好好認識一下它們,
1.理解前端構建程序中的任務
對比于JavaScript的函式,個人對任務是這么分類的:
- 單任務:同步任務和異步任務
- 多任務:并行任務、串行任務
同步任務和異步任務無須解釋,這里說說并行任務和串行任務,任務并行可以用于縮短多個任務的執行時間,因為node是單執行緒執行的,我認為它并不能縮短多個同步任務并行的執行時間,但是構建程序中的任務通常都會涉及到IO流,所以大部分任務都是異步任務,IO任務并行可以避免IO阻塞執行緒執行,進而縮短多個任務的執行時間,
而任務串行可以保證任務之間的有序執行,比如在開發環境下,我們肯定要先執行編譯任務,然后才能執行啟動開發服務器的任務,
理解了構建程序中的任務之后,下面再列舉一些在日常開發當中,我們常見到的構建任務,
2.前端構建程序中的常見任務
| 任務名 | 任務職責 |
|---|---|
| Eslint檢查 | 統一代碼風格 |
| babel轉換 | ES6 -> ES5 |
| typescript轉換 | Ts -> Js |
| sass轉換 | sass -> css |
| less轉換 | less -> css |
| html、css、js壓縮 | .html -> .min.html、.css->.min.css、.js->.min.js |
| web server | 開發服務器 |
| js、css兼容 | 兼容不同瀏覽器 |
| … | … |
除了上述表格中列舉的任務之外,在不同的專案中還會有不同的構建任務,這里不再一一贅述,上面說到自動化構建其實就是一個任務流,再理解和認識了常見任務之后,我們再來理一理什么是前端的構建任務流,
3.理解前端構建任務流
構建是為工程服務的,而工程又是為用戶服務的,對應于開發環境和生產環境,前端工程可以分為開發環境工程和生產環境工程,其中開發環境工程為開發者服務,生產環境工程為用戶服務,
滿足工程使用者的需求是我們構建工程的終極目的,所以有必要投其所好,根據工程的使用者不同,完成他所需要的的一連串任務,也就是任務流,這時可以根據構建后工程的目標使用者來劃分,把任務流分為開發環境構建任務流和生產環境構建任務流兩種,
4.理解開發環境構建任務流
開發環境構建任務流構建后的工程是為開發者服務的,開發者需要開發除錯代碼,所以開發環境任務流構建的工程需要實作以下功能:
| 功能項 | 包含任務 |
|---|---|
| 語法檢查 | Eslint |
| 語法轉換 | ES6 -> ES5、Sass -> less、Ts->Js等等 |
| 模擬生產環境 | web開發服務器:devServer |
| 易于除錯 | sourceMap |
| … | … |
開發者需要不斷修改代碼查看效果,所以除了滿足功能之外,還需要加快構建速度并且自動重繪,以保證良好的使用體驗,
| 優化方式 | 實作方案 |
|---|---|
| 加快構建速度 | devServer熱模塊替換 |
| 自動重繪 | devServer 監聽源代碼 |
| … | … |
關于web開發服務器devServer
使用web開發服務器可以模擬像使用nginx、tomcat等服務器軟體一樣的線上環境,它在功能以及配置上都與nginx以及tomcat類似, 最簡單的配置就是指明資源路徑baseUrl以及服務啟動ip和埠port即可,在開發環境啟動本地服務時,配置代理可以在符合同源策略的情況下解決跨域問題,
開發服務器除了可以模擬線上環境之外,更加強大的一點是它可以監聽源代碼,實作熱部署和自動重繪功能!
5.理解生產環境構建任務流
生產環境構建任務流構建后的工程是為用戶服務的,與開發環境相比,它也需要語法檢查以及編譯功能,但不需要考慮修改以及除錯代碼的問題,它關注的是瀏覽器兼容以及運行速度等問題,
| 功能項 | 包含任務 |
|---|---|
| 語法檢查 | Eslint |
| 語法轉換 | ES6 -> ES5、Sass -> less、Ts->Js等等 |
| 語法兼容 | 不同瀏覽器的js、css語法兼容 |
| 下載速度 | 資源壓縮與合并 |
| … | … |
生產環境的優化除了資源的下載速度之外,還可以從很多方面入手,下面是其中的一些方面以及實作方案,
| 優化方面 | 實作方式 |
|---|---|
| 下載優化 | treeshaking、代碼分割、懶加載 |
| 運行優化 | 代碼上優化性能 |
| 離線訪問 | pwa技術 |
| … | … |
終于把任務以及任務流淺顯粗陋的講完了,接下來我們先是使用npm scripts來實作簡單專案的自動化構建,而后學習一下Gulp工具如何實作復雜專案的自動化構建,
二:npm script實作自動化構建任務流
任務流由任務組成,任務由腳本實作,在定義好任務腳本或者安裝好任務cli模塊之后,我們只需在package.json的scripts選項中配置一條script,就可以方便地呼叫任務腳本或者任務模塊,對于任務流的npm script定義,我們可以借助一些可以幫助任務組合的庫,這樣就可以實現多個任務之間的并行和串行,
這里不得不提一下node_modules/.bin檔案夾,我們在專案中安裝的cli模塊都會有一個cmd檔案出現在這里,當我們在專案中需要呼叫這些cli模塊時,只需yarn/npx cli模塊名的方式就可以很方便的呼叫這些cli模塊,
好的,通過上面的分析之后,我們接下來展開講述一下npm scripts如何實作任務以及任務流的構建,
1.npm script實作任務、任務流構建
單任務構建
對于單任務的構建,只需配置一條簡單的script即可,如以下sass和ES6轉換的script示例(package.json):
"scripts": {
"sass": "sass scss/main.scss css/style.css",
"es6": "babel es6 --out-dir es5",
},
"devDependencies": {
"@babel/cli": "^7.12.8",
"@babel/core": "^7.12.9",
"sass": "^1.29.0"
}
配置以上scripts之后,我們就可以使用以下命令執行任務:
# sass轉換
yarn sass
# es6轉換
yarn es6
這里提一提在執行上述命令之后到最后呼叫sass和es6編譯工具的呼叫程序:
-
yarn sass -> sass scss/main.scss css/style.css -> node_modules/.bin/sass.cmd -> node_modules/sass/sass.js -> …code
-
yarn es6 -> babel es6 --out-dir es5 -> node_modules/.bin/babel.cmd -> node_modules/@babel\cli\bin\babel.js -> node_modules/@babel\cli\lib\index.js -> …code
任務流構建
對于任務流的構建,除了準備基本任務之外,我們還需要考慮這些任務之間是否有序,如果有序我們借助任務串行實作,如果無序我們通過任務并行加快構建速度,通常我們會借助npm-run-all 這個庫來實作任務的并行和串行,如以下通過任務并行實作sass轉換以及ES6轉換的簡單示例(package.json):
"scripts": {
"sass": "sass scss/main.scss css/style.css",
"es6": "babel es6 --out-dir es5",
"build": "run-p sass es6"
},
"devDependencies": {
"npm-run-all": "^4.1.5",
"@babel/cli": "^7.12.8",
"@babel/core": "^7.12.9",
"sass": "^1.29.0"
}
配置以上scripts之后,我們就可以使用以下命令執行任務:
yarn build
執行yarn build之后,就可以借助npm-run-all庫的nun-p.cmd實作sass和es6任務的并行,對于任務的串行,則通過npm-run-all庫的nun-s.cmd實作,
好的,在通過以上兩個示例理解了npm script實作構建任務以及任務流之后,我們接下來通過npm script實作一個簡單前端專案的開發環境自動化構建和生產環境的自動化構建,
2.npm scripts構建工程示例
1):需構建的專案源代碼

這個專案很簡單,它只包含一個html檔案,一個使用了ES6語法js檔案以及一個使用了sass語法的樣式檔案,接下來我們就用npm script來實作這個簡單專案的自動化構建(也即開發環境構建任務流和生產環境構建任務流),事實上,簡單專案的自動化構建就是npm script實作自動化構建的使用場景,
2):實作開發環境自動化構建任務流
通過上面我們對開發環境構建任務流的認識,我們先理一理在這個專案中,開發環境任務流至少應該包含哪些任務(不全,根據上面表格,此處應該能有更多任務):
- 需要web開發服務器模擬生產環境以及實作原始碼監聽和自動重繪,
- 對于sass檔案,需要實時sass轉換,并且監聽原始碼變化重啟開發服務器、重繪瀏覽器,
- 對于ES6檔案,需要實時babel轉換,并且監聽原始碼變化重啟開發服務器、重繪瀏覽器,
- 對于html檔案,需要監聽原始碼變化重啟開發服務器、重繪瀏覽器,
對于sass和ES6修改源代碼后的實時轉換,我們可以通過加上一個watch引數實作,而對于所有這些需要監聽變化的檔案,我們則統一放入temp檔案夾下(角色好比如nginx和Tomcat的應用存放目錄),而后讓web開發服務器監聽這個temp檔案夾下所有檔案的變化,一旦變化即重啟并重繪瀏覽器,
好的經過上面任務分析之后,我們可能會把package.json的scripts以及devDependencies寫成如下樣子:
"scripts": {
"sassDev": "sass scss/main.scss temp/css/style.css --watch",
"babelDev": "babel es6/script.js --out-dir temp/es5/script.js --watch",
"copyHtmlDev": "copyfiles index.html temp",
"serve": "browser-sync temp --files \"temp\"",
"start": "run-p sassDev babelDev copyHtmlDev serve"
},
"devDependencies": {
"@babel/cli": "^7.12.8",
"@babel/core": "^7.12.9",
"browser-sync": "^2.26.13",
"copyfiles": "^2.4.1",
"npm-run-all": "^4.1.5",
"sass": "^1.29.0"
}
3):實作生產環境自動化構建任務流
通過上面我們對生產環境構建任務流的認識,我們先理一理在這個專案中,生產環境任務流應該包含哪些任務(不全,根據上面表格,此處應該能有更多任務):
"scripts": {
"sass": "sass scss/main.scss dist/css/style.css",
"babel": "babel es6 --out-dir dist/es5",
"copyHtml": "copyfiles index.html dist",
"build": "run-p sass babel copyHtml"
},
"devDependencies": {
"@babel/cli": "^7.12.8",
"@babel/core": "^7.12.9",
"browser-sync": "^2.26.13",
"copyfiles": "^2.4.1",
"npm-run-all": "^4.1.5",
"sass": "^1.29.0"
}
按道理說,在生產環境下,至少需要做代碼的兼容以及壓縮,這時我們就需要找到對應的工具庫或者自己實作,另外對于壓縮而言至少需要在編譯之后完成,所以需要注意多個任務間的關系,思路很簡單,博主偷個懶當前就不花時間去實踐了,需要時再實作就行,
3.npm scripts構建總結
在介紹Gulp之前,我們有必要再重申一點,在專案以及構建需求不復雜時,npm scripts就可以滿足我們的構建需求了,無需借助其它工具,
三:成熟工具Gulp實作自動化構建任務流
1.簡單認識Gulp
Gulp是一個基于流的自動化構建工具,相比較于Grunt,它的構建速度更快,任務撰寫也更加簡單靈活(Grunt博主沒用過也不感興趣),要使用Gulp,首先需要在專案根目錄下創建一個Gulp入口檔案gulpfile.js,然后在這個入口檔案中通過暴露函式的方式注冊任務,
對于一個工具,其它的不多比比,我們接下來看看它是怎么實作前端專案的自動化構建的,
2.Gulp實作任務、任務流構建
1):實作同步任務和異步任務
對于新版本的Gulp來說,所有任務都是異步任務,所以任務需要告訴Gulp什么時候執行結束,以下是gulp異步任務實作的幾種方式(關注它們是如何通知Gulp異步任務結束的),
// 方式1:呼叫done方法主動通知任務結束
exports.foo = done => {
console.log('foo task working~')
done() // 標識任務執行完成
}
// 方式2:回傳Promise,通過它的resolve/reject方法通知任務結束
const timeout = time => {
return new Promise(resolve => {
setTimeout(resolve, time)
})
}
// 方式3:回傳讀取流物件,流完即自動通知任務結束
exports.stream = () => {
const read = fs.createReadStream('yarn.lock')
const write = fs.createWriteStream('a.txt')
read.pipe(write)
return read
}
// 更多方式
2):實作并行任務和串行任務
并行任務和串行任務可以通過gulp提供的series(串行), parallel(并行)實作,
const { series, parallel } = require('gulp');
const task1 = done => {
setTimeout(() => {
console.log('task1 working~');
done();
}, 1000)
}
const task2 = done => {
setTimeout(() => {
console.log('task2 working~');
done();
}, 1000)
}
exports.bar = parallel(task1, task2); // 并行任務bar
exports.foo = series(task1, task2); // 串行任務foo
3):Gulp插件任務和自定義任務
插件任務
Gulp生態中有很多成熟的gulp任務插件,使用它們可以很好地提高效率,如以下示例:
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插件,那么我們就需要自定義任務,如下示例:
const fs = require('fs')
const { Transform } = require('stream')
exports.default = () => {
const readStream = fs.createReadStream('normalize.css');
const writeStream = fs.createWriteStream('normalize.min.css');
// 檔案轉換流
const transformStream = new Transform({
// 核心轉換程序
transform: (chunk, encoding, callback) => {
const input = chunk.toString();
const output = input.replace(/\s+/g, '').replace(/\/\*.+?\*\//g, '');
callback(null, output);
}
})
return readStream
.pipe(transformStream) // 轉換
.pipe(writeStream) // 寫入
}
3.Gulp實作開發環境和生產環境自動化構建
gulp只是一個幫助我們實作自動化構建的工具,思想在上文中已經探討了很多,而且對比于gulp,個人對后面的webpack更感興趣,這個示例我就不多做敘述了,
好吧,都是借口,現在已經深夜了,用心地寫了那么長,我坦白我熬不住了,對了,如果認可文章內容,點贊收藏鼓勵一下吧,每周一篇,后面會有我的更多學習記錄哦,
下例是課程中老師使用gulp對開發環境和生產環境自動化構建的示例實作:
const { src, dest, parallel, series, watch } = require('gulp')
const del = require('del')
const browserSync = require('browser-sync')
const loadPlugins = require('gulp-load-plugins')
const plugins = loadPlugins()
const bs = browserSync.create()
const data = {
menus: [
{
name: 'Home',
icon: 'aperture',
link: 'index.html'
},
{
name: 'Features',
link: 'features.html'
},
{
name: 'About',
link: 'about.html'
},
{
name: 'Contact',
link: '#',
children: [
{
name: 'Twitter',
link: 'https://twitter.com/w_zce'
},
{
name: 'About',
link: 'https://weibo.com/zceme'
},
{
name: 'divider'
},
{
name: 'About',
link: 'https://github.com/zce'
}
]
}
],
pkg: require('./package.json'),
date: new Date()
}
const clean = () => {
return del(['dist', 'temp'])
}
const style = () => {
return src('src/assets/styles/*.scss', { base: 'src' })
.pipe(plugins.sass({ outputStyle: 'expanded' }))
.pipe(dest('temp'))
.pipe(bs.reload({ stream: true }))
}
const script = () => {
return src('src/assets/scripts/*.js', { base: 'src' })
.pipe(plugins.babel({ presets: ['@babel/preset-env'] }))
.pipe(dest('temp'))
.pipe(bs.reload({ stream: true }))
}
const page = () => {
return src('src/*.html', { base: 'src' })
.pipe(plugins.swig({ data, defaults: { cache: false } })) // 防止模板快取導致頁面不能及時更新
.pipe(dest('temp'))
.pipe(bs.reload({ stream: true }))
}
const image = () => {
return src('src/assets/images/**', { base: 'src' })
.pipe(plugins.imagemin())
.pipe(dest('dist'))
}
const font = () => {
return src('src/assets/fonts/**', { base: 'src' })
.pipe(plugins.imagemin())
.pipe(dest('dist'))
}
const extra = () => {
return src('public/**', { base: 'public' })
.pipe(dest('dist'))
}
const serve = () => {
watch('src/assets/styles/*.scss', style)
watch('src/assets/scripts/*.js', script)
watch('src/*.html', page)
// watch('src/assets/images/**', image)
// watch('src/assets/fonts/**', font)
// watch('public/**', extra)
watch([
'src/assets/images/**',
'src/assets/fonts/**',
'public/**'
], bs.reload)
bs.init({
notify: false,
port: 2080,
// open: false,
// files: 'dist/**',
server: {
baseDir: ['temp', 'src', 'public'],
routes: {
'/node_modules': 'node_modules'
}
}
})
}
const useref = () => {
return src('temp/*.html', { base: 'temp' })
.pipe(plugins.useref({ searchPath: ['temp', '.'] }))
// html js css
.pipe(plugins.if(/\.js$/, plugins.uglify()))
.pipe(plugins.if(/\.css$/, plugins.cleanCss()))
.pipe(plugins.if(/\.html$/, plugins.htmlmin({
collapseWhitespace: true,
minifyCSS: true,
minifyJS: true
})))
.pipe(dest('dist'))
}
const compile = parallel(style, script, page)
// 上線之前執行的任務
const build = series(
clean,
parallel(
series(compile, useref),
image,
font,
extra
)
)
const develop = series(compile, serve)
module.exports = {
clean,
build,
develop
}
本文結束,謝謝觀看,
如若認可,點贊收藏,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/229875.html
標籤:其他
下一篇:在原生開發中控制HTML5視頻
