前端工程化實戰
技術標籤:前端# javascript
前端工程化實戰
工程化概述
1.工程化的定義和主要解決的問題
- 傳統語言或語法的弊端
- 無法使用模組化。元件化
- 重複的機械工作
- 程式碼風格的統一,質量保證
- 依賴後端服務介面的支援
- 整體依賴後端專案
2.一個專案過程工程化的表現
一切重複的工作都應該被自動化
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-JlvxiaGI-1610480594072)(C:\Users\Admin\AppData\Roaming\Typora\typora-user-images\1608718394556.png)]
- 建立專案
- 建立專案結構
- 建立特定型別檔案
- 編碼
- 格式化程式碼
- 校驗程式碼風格
- 編譯構建和打包
- 預覽和測試
- webServer/Mock
- live Reloading/HMR
- Source Map
- 提交
- git hooks
- lint-staged
- 持續整合
- 部署
- CI/CD
- 自動釋出
3.工程化不等於工具
工程化=/=某個工具
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-tfCa5zQ3-1610480594074)(C:\Users\Admin\AppData\Roaming\Typora\typora-user-images\1608718866374.png)]
規劃專案架構
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-JJY3jGI8-1610480594075)(C:\Users\Admin\AppData\Roaming\Typora\typora-user-images\1608718921517.png)]
官方給出的整合式專案解決方案
4.工程化與NodeJS
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-XP7LE8im-1610480594076)(C:\Users\Admin\AppData\Roaming\Typora\typora-user-images\1608719022782.png)]
- 腳手架工具的開發
- 自動化構建系統
- 模組化打包
- 專案程式碼規範化
- 自動化部署
腳手架工具
1.腳手架工具概要
- 腳手架的本質作用—建立專案結構,提供專案規範和約定
- 相同的組織結構,相同的開發正規化,相同的模組依賴,相同的工具配置,相同的基礎程式碼
2.常用的腳手架工具
- react create-react-app
- vue vue-cli
- angular angular-cli
- Yeoman
- Plop
3.Yeoman簡介
Yeoman是一個通用的現代腳手架工具
可以定製屬於自己的前端腳手架
4.Yeoman的基礎使用
-
安裝前需要安裝Node
-
$ yarn global add yo //全域性範圍安裝yo $ yarn global add generator-node//安裝對應的generator $ yo node // 執行generator
5.Sub Generrator
一些淘寶映象源
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-bXvW2xkj-1610480594077)(C:\Users\Admin\AppData\Roaming\Typora\typora-user-images\1608722114871.png)]
yo node:cli
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-zmQoNqHW-1610480594077)(C:\Users\Admin\AppData\Roaming\Typora\typora-user-images\1608721932495.png)]
6.自定義 Generator(基於Yeoman搭建自己的腳手架)
-
建立Generator模組,本質上是建立NPM模組
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-450Ho4BK-1610480594078)(C:\Users\Admin\AppData\Roaming\Typora\typora-user-images\1608722402287.png)]
-
模組名必須是generator-
-
步驟
-
建立模組資料夾 mkdir generator-sample
-
進入模組資料夾:cd generator-sample
-
yarn-init
-
yarn add yeoman-generator// 方便建立生成器
-
在indexjs 是Generator的核心入口,需要繼承 yeoman Generator
-
寫程式碼
const Generator=require('yeoman-generator') module.exports=class extends Generator{ this.fs.write( this.destinationPath('temp.txt'), Math.random().toString() ) }
-
yarn link // 連結到全域性範圍
-
yo sample
-
8.根據模板建立檔案
-
在app資料夾下建立一個資料夾模板
-
在index.js資料夾中
const tmpl = this.templatePath('foo.txt') // 輸出目標路徑 const output = this.destinationPath('foo.txt') // 模板資料上下文 const context = { title: 'Hello zce~', success: false } this.fs.copyTpl(tmpl, output, context)
9.接受使用者輸入
-
使用基類的prompting()
-
prompting() { // Yeoman 在詢問使用者環節會自動呼叫此方法 // 在此方法中可以呼叫父類的 prompt() 方法發出對使用者的命令列詢問 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 }) }
10.Vue-Generatal示例
- 1.新建一個Vue的Generatal的示例
- 2.將Vue基礎的腳手架替換為模板
- 如果遇見<%xxx%>這種欄位需要在前新增%號
- 3.在index.js遍歷每個檔案並生成到輸出目錄下
11.釋出一個Generatal模組
12.Plop簡介
自動化構建
1.自動化 構建的簡介
- 自動化構建 ,原始碼 變成生產環境的程式碼
- 將一些不被支援的特性轉換為可以支援的
2.初步認識自動化構建
3.常用的自動化構建工具
-
grunt
構建速度相對較慢
-
Gulp
同時執行多工
在記憶體中讀寫-塊
-
FIS
初學者比較適
4.Grunt 的基本使用
-
$ yarn --init code gruntfile.js // 新增Grunt的入口 檔案
5.Grunt標記任務失敗
- 使用renturn false
- 前面的任務執行了 後面任務預設不執行
- –force 在終端輸入可以讓所有執行
module.exports = grunt => {
// 任務函式執行過程中如果返回 false
// 則意味著此任務執行失敗
grunt.registerTask('bad', () => {
console.log('bad working~')
return false
})
grunt.registerTask('foo', () => {
console.log('foo working~')
})
grunt.registerTask('bar', () => {
console.log('bar working~')
})
// 如果一個任務列表中的某個任務執行失敗
// 則後續任務預設不會執行
// 除非 grunt 執行時指定 --force 引數強制執行
grunt.registerTask('default', ['foo', 'bad', 'bar'])
// 非同步函式中標記當前任務執行失敗的方式是為回撥函式指定一個 false 的實參
grunt.registerTask('bad-async', function () {
const done = this.async()
setTimeout(() => {
console.log('async task working~')
done(false)
}, 1000)
})
}
6.Grunt 配置選項方法
module.exports = grunt => {
// grunt.initConfig() 用於為任務新增一些配置選項
grunt.initConfig({
// 鍵一般對應任務的名稱
// 值可以是任意型別的資料
foo: {
bar: 'baz'
}
})
grunt.registerTask('foo', () => {
// 任務中可以使用 grunt.config() 獲取配置
console.log(grunt.config('foo'))
// 如果屬性值是物件的話,config 中可以使用點的方式定位物件中屬性的值
console.log(grunt.config('foo.bar'))
})
}
7.Grunt多目標任務
8.Grunt外掛的使用
//1.使用npm安裝外掛
//2.GruntFile檔案中grunt.loadNpmTasks('外掛名')
module.exports = grunt => {
grunt.initConfig({
clean: {
temp: 'temp/**'
}
})
grunt.loadNpmTasks('grunt-contrib-clean')
}
9.Grunt常用的外掛
// grunt-babel
// grunt-contrib-watch
//grunt-sass
//load-grunt-task
//sass
const sass = require('sass')
const loadGruntTasks = require('load-grunt-tasks')
module.exports = grunt => {
grunt.initConfig({
sass: {
options: {
sourceMap: true,
implementation: sass
},
main: {
files: {
'dist/css/main.css': 'src/scss/main.scss'
}
}
},
babel: {
options: {
sourceMap: true,
presets: ['@babel/preset-env']
},
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']) //
}
Gulp基本使用—高效 易用
-
安裝依賴
-
yarn init --yes yarn add gulp --dev code gulpfile.js
-
//每一個任務都是非同步的 //需要有一個引數標識任務的完成
-
// // 匯出的函式都會作為 gulp 任務 // exports.foo = () => { // console.log('foo task working~') // } // gulp 的任務函式都是非同步的 // 可以通過呼叫回撥函式標識任務完成 exports.foo = done => { console.log('foo task working~') done() // 標識任務執行完成 } // default 是預設任務 // 在執行是可以省略任務名引數 exports.default = done => { console.log('default task working~') done() } // v4.0 之前需要通過 gulp.task() 方法註冊任務 const gulp = require('gulp') gulp.task('bar', done => { console.log('bar task working~') done() })
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) } const task3 = done => { setTimeout(() => { console.log('task3 working~') done() }, 1000) } // 讓多個任務按照順序依次執行 exports.foo = series(task1, task2, task3) // 讓多個任務同時執行 exports.bar = parallel(task1, task2, task3)
Gulp非同步任務的三種方式
-
通過回撥函式解決—錯誤優先的標準
-
Promise的方案
-
Async await
-
stream
-
const fs = require('fs') exports.callback = done => { console.log('callback task') done() } exports.callback_error = done => { console.log('callback task') done(new Error('task failed')) } exports.promise = () => { console.log('promise task') return Promise.resolve() } exports.promise_error = () => { console.log('promise task') return Promise.reject(new Error('task failed')) } const timeout = time => { return new Promise(resolve => { setTimeout(resolve, time) }) } exports.async = async () => { await timeout(1000) console.log('async task') } exports.stream = () => { const read = fs.createReadStream('yarn.lock') const write = fs.createWriteStream('a.txt') read.pipe(write) return read } // exports.stream = done => { // const read = fs.createReadStream('yarn.lock') // const write = fs.createWriteStream('a.txt') // read.pipe(write) // read.on('end', () => { // done() // }) // }
Gulp構建過程核心工作原理
讀檔案–改變檔案—寫檔案
基於Stream的流讀取
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) // 寫入
}
Gulp檔案操作API
一些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樣式編譯
const style = () => {
return src('src/assets/styles/*scss', { base: "src" })
.pipe(sass({ outputStyle: 'expanded' }))
.pipe(dest('dist'))
}
Gulp指令碼編譯
const script = () => {
return src("src/assets/scripts/*js", { base: "src" })
.pipe(babel({ presets: ['@babel/preset-env'] }))
.pipe(dest('dist'))
}
Gulp案例 圖片和字型轉換
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'))
}
Gulp其他檔案及檔案清除
const extra = () => {
return src('public', { base: 'src' })
.pipe(dest('dist'))
}
const clean = () => {
return del(['dist'])
}
Gulp自動載入外掛
-
const loadPlugins = require('gulp-load-plugins') const plugins = loadPlugins() // const plugins.sass = require('gulp-sass') // const plugins.babel = require('gulp-babel') // const plugins.swig = require('gulp-swig') // const plugins.imagemin = require('gulp-imagemin')
-
外掛名就是其成員
Gulp開發伺服器
使用browser-sync安裝的
const serve = () => {
bs.init({
notify: false,
port: 2000,
files: 'dist/**',
// open: false,
server: {
baseDir: 'dist',
routes: { //處理請求
'/node_modules': 'node_modules'
}
}
})
}
Gulp監視變化以及構建變化
const serve = () => {
watch('src/assets/styles/*scss', style)
watch('src/assets/scripts/*js', script)
watch('src/*.html', html)
bs.init({
notify: false,
port: 2000,
files: 'dist/**',
// open: false,
server: {
baseDir: 'dist',
routes: { //處理請求
'/node_modules': 'node_modules'
}
}
})
}
Gulpuserf檔案引用處理
const useref = () => {
return src('dist/*html', { base: 'dist' })
.pipe(plugins.useref({ searchPath: ['dist', '.'] }))
.pipe(dest('dist'))
}
Gulp案例檔案壓縮
const useref = () => {
return src('dist/*html', { base: '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,
minifyCss: true,
minifyJS: true
})))
.pipe(dest('release'))
}
Gulp重新構建規劃過程
// 實現這個專案的構建任務
// 實現步驟
/**
* 1.樣式編譯
* 安裝gulp-sass 並引入,sass會預設任務帶下劃線的被引入
* outputStyle: 'expanded' 樣式完全展開
* 2.指令碼編譯
* 安裝 @babel/core @babel/preset-env gulp-babel
* 3.模板檔案編譯
* 安裝 yarn add gulp-swig
* 4.圖片和字型的轉換(壓縮檔案)
* 安裝 gulp-imagemin
* 5.檔案清除和其他 檔案
* 6.自動載入外掛
* 最新版的imagemin 無法自動載入
* 7,熱更新開發伺服器
* 安裝browser-sync ,使用watch監聽檔案變化
* 8.useref外掛
* gulp-useref差距
* 9.壓縮html js css
* gulp-htmlmin gulp-uglify gulp-clean-css --dev
* gulp-if
*/
const { src, dest, parallel, series } = require('gulp')
const loadPlugins = require('gulp-load-plugins')
const plugins = loadPlugins()
// const plugins.sass = require('gulp-sass')
// const plugins.babel = require('gulp-babel')
// const plugins.swig = require('gulp-swig')
// const plugins.imagemin = require('gulp-imagemin')
const del = require('del')
const browserSync = require('browser-sync')
const bs = browserSync.create()
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 style = () => {
return src('src/assets/styles/*scss', { base: "src" })
.pipe(plugins.sass({ outputStyle: 'expanded' }))
.pipe(dest('temp'))
}
const script = () => {
return src("src/assets/scripts/*js", { base: "src" })
.pipe(plugins.babel({ presets: ['@babel/preset-env'] }))
.pipe(dest('temp'))
}
const html = () => {
return src("src/*.html", { base: "src" })
.pipe(plugins.swig({ data }))
.pipe(dest('temp'))
}
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 clean = () => {
return del(['dist', 'temp'])
}
const serve = () => {
watch('src/assets/styles/*scss', style)
watch('src/assets/scripts/*js', script)
watch('src/*.html', html)
watch([
"src/assets/fonts/**",
"src/assets/images/**",
'public/**'
], bs.reload)
bs.init({
notify: false,
port: 2000,
files: 'dist/**',
// open: false,
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, html)
const build = series(clean,
parallel(series(compile, useref),
image,
font,
extra
))
const dev = series(compile, serve)
// const page
module.exports = {
compile,
build,
dev,
serve,
clean
}