1. 程式人生 > 其它 >前端工程化實戰

前端工程化實戰

技術標籤:前端# javascript

前端工程化實戰

工程化概述

1.工程化的定義和主要解決的問題

  • 傳統語言或語法的弊端
  • 無法使用模組化。元件化
  • 重複的機械工作
  • 程式碼風格的統一,質量保證
  • 依賴後端服務介面的支援
  • 整體依賴後端專案

2.一個專案過程工程化的表現

一切重複的工作都應該被自動化

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-JlvxiaGI-1610480594072)(C:\Users\Admin\AppData\Roaming\Typora\typora-user-images\1608718394556.png)]

  1. 建立專案
    • 建立專案結構
    • 建立特定型別檔案
  2. 編碼
    • 格式化程式碼
    • 校驗程式碼風格
    • 編譯構建和打包
  3. 預覽和測試
    • webServer/Mock
    • live Reloading/HMR
    • Source Map
  4. 提交
    • git hooks
    • lint-staged
    • 持續整合
  5. 部署
    • 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搭建自己的腳手架)

  1. 建立Generator模組,本質上是建立NPM模組

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-450Ho4BK-1610480594078)(C:\Users\Admin\AppData\Roaming\Typora\typora-user-images\1608722402287.png)]

  2. 模組名必須是generator-

  3. 步驟

    • 建立模組資料夾 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.根據模板建立檔案

  1. 在app資料夾下建立一個資料夾模板

  2. 在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自動載入外掛

  1. 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')
    
  2. 外掛名就是其成員

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
}