工程化概述
前端工程化事指遵循一定的標準和規(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)流行鉴逞,更適合初學者使用