天天看點

前端工程化第一章: 概述第二章:腳手架(scaffolding) Yeoman第三章:腳手架 Plop第四章:scaffolding 原理第五章:自動化建構話題: 對比grunt gulp fis第七章: Grunt 第八章 - Gulp第九章: 可複用_自動化建構_工作流

第一章: 概述

前端工程化: 提高效率, 降低成本, 保證品質

解決的問題:顔色代表不同問題分類

使用es6 相容, lass sass 等無法直接運作, 子產品化方式無法直接運作, 手動重複性動作(部署/壓縮), 團隊風格統一,github 拉的代碼品質不可保證。    開發/整體依賴後端接口。 

 涉及領域:(對照下圖)

前端工程化第一章: 概述第二章:腳手架(scaffolding) Yeoman第三章:腳手架 Plop第四章:scaffolding 原理第五章:自動化建構話題: 對比grunt gulp fis第七章: Grunt 第八章 - Gulp第九章: 可複用_自動化建構_工作流

第二章:腳手架(scaffolding) Yeoman

本質: 建立項目基礎結構,規範和約定

Yeoman + generators => 各種腳手架

Yoeman 平A 技能 

yeoman  使用 node-generator 建立 node modules 流程

Guohais-MBP:~ guohaiqu$ node -v  |v16.0.0   |v10.16.2
Guohais-MBP:~ guohaiqu$ npm -v   |7.19.0    |6.9.0
Guohais-MBP:~ guohaiqu$ yarn -v  |1.22.10   |1.17.3

Guohais-MBP:~ guohaiqu$ yarn global add yo              |全局安裝 yoeman
Guohais-MBP:~ guohaiqu$ yarn global add generator-node  |全局安裝 generator-node

|進入檔案|

Guohais-MBP:my_module guohaiqu$ yo node

|回答一些問題| => 安裝
           

Yeoman Sub Generator (子生成器)

Guohais-MBP:my_module guohaiqu$ yo node:cli


Overwrite package.json yes

           

報錯: premisson denied  

sudo chmod -R 755 檔案目錄(my_module)
sudo chmod -R 755 檔案目錄(my_module/lib)

yarn unlink 
yarn link
           

使用 yoeman + generator-webapp

yarn global add generator-wbapp
yo webapp
           

自定義Genertator = 建立npm 子產品

        1. 檔案結構 固定(如下圖,是否包含子生成器) 

        2. 檔案名 固定 generator-filename

前端工程化第一章: 概述第二章:腳手架(scaffolding) Yeoman第三章:腳手架 Plop第四章:scaffolding 原理第五章:自動化建構話題: 對比grunt gulp fis第七章: Grunt 第八章 - Gulp第九章: 可複用_自動化建構_工作流
|| terminal
mkdir generator-sample              // 建立生成器子產品的目錄
cd generator-sample                 // 進入檔案夾
yarn init                           // 建立 package.json
yarn add yeoman-generator           // 安裝 子產品 yeoman-generator = 提供生成器 基類 


|| vs code 
generators/app/index.js             // 建立 index.js(generator核心入口)


||index.js
具體功能
           
const Generator = require('yeoman-generator')
module.exports = class extends Generator {
    writing() {
        this.fs.write(
            this.destinationPath('temp.txt'),
            Math.random().toString()
        )
    }
}
           
Guohais-MacBook-Pro:generator-sample guohaiqu$ yarn link
cd ..
mkdir my-test
cd my-test
yo sample
           

添加模版檔案

app/templates/foo.txt 

//   模版檔案建立   
     writing() {
        const tmpl = this.templatePath('foo.txt')
        const output = this.destinationPath('foo.txt')
        const context = { title:'hello', success:false }
        this.fs.copyTpl(tmpl, output, context)
    }
           
//  指令行互動方法    
    prompting () {
        return this.prompt([
            {
                type: "input",
                name: "title",
                message: "your project name", // 提示
                default: this.appname // 項目目錄名
            },
            {
                type: "input",
                name: "gg",
                message: "second", // 提示
                default: "gg"
            }
        ])
        .then (answers =>{
            this.answers = answers
            console.log(this.answers) //{ title: 'ggg', gg: 'yyy' }
        })
    }
// in foo.txt : eg: <%= title %><%= gg %>
           

批量添加模版檔案

const Generator =  require('yeoman-generator')

module.exports = class extends Generator {
    prompting() {
        return this.prompt([
            {
                type:"input",
                name:"project_name",
                message:"enter project name",
                default: this.appname            
            }
        ])
        .then(answers => {
            this.answers = answers
        })
    }
    writing() {
        const templates = [
            'public/first.html',
            'public/first.css',
            'src/second.html',
            'src/second.css'
        ]
        templates.forEach(item => {
                this.fs.copyTpl(
                this.templatePath(item),
                this.destinationPath(item),
                this.answers
            )
        });
    }
}
           
前端工程化第一章: 概述第二章:腳手架(scaffolding) Yeoman第三章:腳手架 Plop第四章:scaffolding 原理第五章:自動化建構話題: 對比grunt gulp fis第七章: Grunt 第八章 - Gulp第九章: 可複用_自動化建構_工作流

.gitignore 忽略 node_modules

第三章:腳手架 Plop

是: 配合項目使用的小型腳手架

執行個體: react + plop 在react 中使用 plop 建立檔案

1. 作為項目以來安裝 plop  | yarn add plop --dev |

2. 項目根下 :建立 plopfile.js , plop入口檔案, 定義腳手架

3. 編寫模版 hbs   | plop-teamplates > filename.filetype.hbs  ( handle bar)

4. plop cli 運作 腳手架  |yarn plop component/生成器名|

plopfile.js

plopfile.js :導出一個函數 ,| module.exports = () => {} |

其接收一個plop 對象(形式參數), 用來建立 generator  | module.exports = plop => {} |

plop 對象 : > (成員) plop.setGenerator (參數1: 生成器名string , 參數2: 配置選項object)

配置選項  :  description「string」 | prompts「array > object」 |actions 「array > object」

npx create-react-app my-app
           
yarn add plop --dev
           
module.exports = plop => {
    plop.setGenerator ('component', { 
        description : "create component",
        prompts: [
            {
                type: "input",
                name: "name",
                message: "component name",
                default: "myComponent"
            }
        ],
        actions: [
            {
                type: "add",
                path: "src/components/{{name}}/{{name}}.js",
                templateFile: "plop-templates/component.js.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.js.hbs"
            },
        ] 
    })
}
           

第四章:scaffolding 原理

建立項目 - 添加package.json 檔案 - 添加"bin": "cli.js" 字段(指定cli 應用入口檔案)

cli應用:  必備的兩個條件 如下

#!/usr/bin/env node  |必須添加檔案頭|
           
Guohais-MacBook-Pro:scaffolding-logic guohaiqu$ chmod 755 cli.js

「mac linux 修改 755 權限」
           

調用:

yarn link 

scaffolding-logic(檔案名)
           

step 1 : scaffolding logic - 詢問資訊

yarn add inquirer
           
const inquirer = require("inquirer")
inquirer.prompt([
    {
        type:"input",
        name: "name",
        message:"project name"
    }
])
.then(anwsers =>{
    console.log(anwsers)
})

// in console: scaffloding-logic (FILENAME)
           

step 2 : scaffolding logic - 添加檔案(讀取 + 寫入)

const path = require("path")  // 讀取路徑
const fs = require("fs")      // 讀取檔案
const ejs = require("ejs")    // 渲染檔案
  

  const tmpDir = path.join(__dirname, 'templates') 
    const distDir = process.cwd() // node process cwd 擷取目前工作環境

    fs.readdir(tmpDir,(err, files)=>{
        if(err) throw err
        files.forEach(file => {
            ejs.renderFile(path.join(tmpDir, file), anwsers,(err, result)=>{
                if(err) throw new err
                fs.writeFileSync(path.join(distDir,file),result) //絕對路徑,檔案内容
            })
        }) 
       
    })
           

第五章:自動化建構

一句話概述:自動轉換代碼| 如:scss - css

npm scripts 可自動發現 node_modules 下的指令, 在 bin 下面

yarn add sass --dev

//without npm scripts
./node_modules/.bin/sass scss/main.scss css/style.css
 
// with npm scripts

scripts:{
    // NPM SCRIPTS : 自動找到node_modules\bin
    build: "sass scss/main.scss css/style.css" 
}

npm run build
           

測試伺服器子產品

yarn add browser-sync --dev
           

ATTENTION!

<head>

        <style media="screen" type="text/css">

                @import "css/style.css";

        </style>

</head>

開啟伺服器前 需要先編譯scss

 方式1 : hook 

"scripts": {
    "build": "sass scss/main.scss css/style.css",
    "preserve":"yarn build",
    "serve":"browser-sync ."
  },
           

 方式2 : npm-run-all 子產品

yarn add npm-run-all --dev

"scripts": {
    "build": "sass scss/main.scss css/style.css --watch",
    "serve": "browser-sync . --files \"css/*.css\"",
    "start": "run-p build serve".  
},

yarn start

解釋: 
scripts: {                   | 自動查找 bin 下的指令
    --watch                  | 監聽scss的檔案變化, 改變就轉譯
    --files \"css/*.css\"    | 監聽檔案變化,改變就重新整理頁面
    -- run-p                 | 同時運作多個指令 
}
           

話題: 對比grunt gulp fis

grunt :速度慢|磁盤讀寫|生态完善|急于臨時檔案 | 微核心|靈活

gulp  :速度相對塊 |記憶體處理|預設多任務同時執行|易懂 |生态完善 |微核心|靈活

fis     :捆綁套餐 |适合新手 |大而全 |适合初學者

第七章: Grunt

mkdir grunt                    | 建立檔案目錄

cd grunt                       | 進入項目

yarn init                      | 初始化package.json

yarn add grunt                 | 安裝 grunt 子產品

touch gruntfile.js             | 建立 grunt 入口檔案
           

 gruntfile.js 入口檔案 說明

1. gruntfile.js :導出一個函數 ,| module.exports = () => {} |

2. 其接收一個grunt 形式參數 | module.exports = grunt => {} |

3. grunt : > (成員) grunt.registerTask (參數1: 任務名 ,參數2(可選):任務描述stringm 參數3: 函數)

4. grunt.registerTask(‘default’,【“任務1”,“任務2”】)

備注: grunt 預設同步模式

備注:grunt 任務名 |執行特定任務

備注:grunt  |執行預設任務

const { registerTask } = require("grunt")            |導入grunt

module.exports = grunt => {
    grunt.registerTask('foo','its a foo' , ()=>{     |同步任務
        console.log('gg')
        return false                                 |同步任務标記失敗

    })

    grunt.registerTask('bar', function(){            |異步任務
        const done = this.async()
        setTimeout(()=>{
            console.log("2s delay")
            done()
            done(false)                              |異步任務标記失敗
        }, 1000)
    })

    grunt.registerTask('default', ['foo','bar'])     |預設任務
}

// yarn grunt <function-name> | --force
           

多目标任務 和 配置選項

 grunt.registerMutiTask ('參數1:任務名 ', 參數2: 函數 )

grunt.initConfig ({

        任務名:{

                options:{

                        foo: 'bar'

                }

                目标名1 : {

                        options: {

                                foo:'baz'  // 覆寫

                        }

                }

                目标名2  : ‘目标2 執行的 任務’

        }

})

${this.target}    | 擷取目标名

${this.data}     |擷取值的名

grunt.initConfig({
        build: {
            options: {
                foo: 'baz'    |為build 添加配置選項
            },
            js: {
                options:{
                    foo:'bar' | 覆寫外圈的foo
                },
                val: 'something'
            },
            css: '123'
        }
    })

grunt.registerMultiTask('build', function(){
    console.log(this.options())        |this.options是一個函數,調用他得到配置選項            
    console.log(this.data)
})
           
yarn grunt build

Running "build:js" (build) task
{ foo: 'bar' }
{ options: { foo: 'bar' }, val: 'gg' }

Running "build:css" (build) task
{ foo: 'baz' }
123
           

Grunt 插件

1. 安裝插件 |2. 導入插件  |3. 配置選項

//1. 安裝
 yarn add grunt-contrib-clean

2. 導入
grunt.loadNpmTasks('grunt-contrib-clean')

3. 配置選項
grunt.initConfig({
    clean: {
        temp:'temp'
    }
})

4. 使用
yarn grunt clean
           
yarn add grunt-sass sass --dev        |grunt-sass 需要依賴sass 來完成 

const sass = require('sass')          |導入sass 子產品

grunt.initConfig({
   
     sass:{
            options: {                         
                sourceMap: true,
                implementation: sass          
            },
            main:{
                files:{
                    'dist/css/main.css':'src/scss/main.scss'
                }
            }
    }
})

grunt.loadNpmTasks('grunt-sass')      |導入grunt-sass 任務
           
yarn add grunt-babel @babel/core @babel/preset-env --dev
           
grunt.initConfig {
    main: {
        options:{
            sourceMap: true,
            presets: ['@babel/preset-env']     |最新 ECMA 特性
        },
        files:{
            'dist/js/app.js':'src/js/app.js'
        }
    }
}
           
grunt-contrib-watch --dev

grunt.initConfig({
    watch:{
        js:{
            files: ['src/js/*js'],                  | 監視 js 檔案
            tasks: ['babel']                        | 執行 babel 任務
        }
    }
})

grunt.registerTasks('default' ['babel', 'watch'])   | 開始時先編譯一下再監視
           
yarn add load-grunt-tasks --dev

const loadGruntTasks = require ('load-grunt-tasks')

loadGruntTasks(grunt) 
           

 第八章 - Gulp

預備: 安裝 glup --dev| 2. gulpfile.js |gulp commands (yarn gulp <任務名>)

yarn init --yes
yarn add gulp --dev
touch gulpfile.js
           

導出任務

exports.foo = done =>{           // 傳入異步任務
    console.log('foo')
    done()                       // 标記異步任務任務完成
}

exports.default = done =>{       // 預設任務
    console.log('default')
    done()                       // 标記異步任務任務完成
}

const gulp = require ('gulp')    // 以前的方法
gulp.task('bar',done=>{
    console.log('working')
    done()
})
           

 series + parallel 任務 

const {series, parallel} = require('gulp') |導入順序和并行執行

注: 未被導出的任務被視為私有
const task1 = done=>{
    setTimeout(() => {
        console.log('task1')
        done()
    },1000)
}

exports.foo = series(task1, task2, task3)
exports.bar = parallel(task1, task2, task3)
           

異步任務

//1. 回調函數實作異步任務

exports.callback = done =>{
    console.log('callback function')
    done()
}

exports.callback_err = done =>{
    console.log('callback function')
    done(new Error('task failed!'))   //錯誤優先,第一個參數時錯誤 // 終止後續任務
}


2. promise 實作異步

exports.promise_error = () =>{
    console.log('promise task!')
    return Promise.reject(new Error('task failed!'))   // 終止後續任務
   // return Promise.resolve()       // gulp 會忽略值, 是以留白就可

}


// 3. async await       node環境限制   version>8 
const timeout = time => {
    return new Promise(resolve =>{
        setTimeout(resolve, time)
    })
}

exports.async = async()=>{
    console.log(async)
    await timeout(1000)      // await 異步任務(promise 對象)
}


4. stream    最常見方式

exports.stream = () => {
    const readStream = fs.createReadStream('package.json')
    const writeStream = fs.createWriteStream('temp.txt')
    readStream.pipe(writeStream)
    // done()
    // return readStream
    //readStream.on('end',()=>{
    //    done()
    //})
}
           

核心原理

讀取 轉換 寫入 :基于 流 的操作

const  fs = require('fs')
const {Transfrom} = require ('stream')

exports.default =()=> {                              導出預設任務
const read = fs.createReadStream(' file_url')        建立檔案讀取流
const write = fs.createWriteStream(' file_url')      建立檔案讀取流 
const transform = new Transform({                    建立檔案轉換流
        transform:(chunk, encoding, callback) {
                const input = chunk.toString()
                const output = input.reaplace('正則')
        }
})
read
    .pipe(transform)
    .pipe(write)
} 
           

Gulp 插件使用:

 { src dest } , gulp-clean-css, gulp-rename 

安裝 | 導入|放入pipe(something(明确參數))

const {src, dest} = require('gulp')
const cleanCss = require('gulp-clean-css')
const rename = require('gulp-rename')

exports.default = () => {
    return src('src/normalize.css')
    .pipe(cleanCss())
    .pipe(rename({extname:'.min.css'}))
    .pipe(dest('dist'))
}
           

Gulp 案例:scss  > css (gulp-sass)

{ base: "src" }, 導出檔案的檔案架構保留

sass({outputsytle: "expanded"}),使用sass 導出css 時的檔案格式

const {src, dest} = require('gulp')
//const sass = require('gulp-sass')                       // gulp-sass 導入
const sass = require('gulp-sass')(require('sass'));     // gulp-sass 5


const style = ()=>{
    src('src/styles/scss/*.scss', {base: 'src'})        // 保留内部檔案路徑
        .pipe(sass({outputsytle: "expanded"}))          // css書寫格式
        .pipe(dest('dist'))
}

module.exports = {                                     // = exports.style 另一種寫法
    style
}
           

Gulp 案例:ECMA (gulp-babel)

gulp-babel 是一個轉換 平台

需要安裝@babel/core  @babel/preset-env 來轉換(babely具體實施)

yarn add gulp-babel --dev   
yarn add @babel/core @babel/preset-env --dev

const babel = require('gulp-babel');
const script = () => {
    return src('src/assets/scripts/*.js', {base: "src"})
            .pipe(babel({presets:['@babel/preset-env']}))
            .pipe(dest('dist'))
}

module.exports = {
    script
}
           

Gulp 案例:HTML (gulp-swig)

傳入需要的 資料, 定義 data 對象, 傳入 swig()
const {src, dest} = require('gulp')
const swig = require('gulp-swig')

const data = {
    menus: [{},{},{}],
    pkg: require('./package.json'),
    data: new Data()
}

const page = () => {
    src('src/**/*html')
        .pipe(swig({defaults:{cache:false}})) // 加入清除緩存
        //.pipe(swig({data}))   //傳入資料
        .pipe(dest('dist'))
}

module.exports = {
    page
}
           

Gulp 案例:ALL TOGETHER

const compile = parallel(style, script, page)

module.exports = {
   compile
}

// yarn gulp compile
           

Gulp 案例:圖檔壓縮 (gulp-imagemin)

yarn add gulp-imagemin --dev

const imagemin = require ('gulp-imagemin')

const image = () =>{
    return src('src/**', {base: 'src'})
            .pipe(imagemin())
            .pipe(dest('dist'))

}

module.exports = {
    image
}

// yarn gulp image
           

Gulp 案例:複制 + 删除 ( del :不是gulp插件)

其他檔案如 public 下的 直接 複制源檔案, src('....'). pipe(dest('dist'))

删除: yarn add del --dev

build 前删除 dist, 確定不覆寫

const del = require('del')

cosnt clean = () => {
    return del(['dist'])
}

const build = series(clean, compile)
           

Gulp 案例: 自動加載插件 (grunt-load-plugins)

//隻适用于 grunt 插件

​
const loadPlugins = require( 'grunt-load-plugins' )

const plugins = loadPlugins()

省略了: const imagemin = require( 'grunt-imagemin' )

使用插件時:pipe( plugins.imagemin() )

而不是: pipe(imagemin())
           

Gulp 案例: dev-server (browser-sync --dev: 不是gulp插件)

導入插件 | 建立伺服器 |建立函數 | 定義配置

配置: notify |port |files|server(baseDir,r)|

const browserSync = require('browser-sync')  // 引入 browser-sync
const bs = browserSync.create()              // 建立伺服器

const serve = ()=> {
    bs.init({
        notify: false,                        // 開啟顯示
        port: 2080,                           // 設定伺服器端口 
        files: "dist/**",                     // 監視dist 下檔案的變化
        server: {
            baseDir: "dist",                  // 指定檔案路徑
            routes: {
                '/node_modules': 'node_modules'  
            }
        }
    })
}
           

監視dist 檔案

添加init 配置, 監視檔案

files: "dist/**",   // 監視dist 下檔案的變化 重新整理頁面
           

Gulp 案例: dev-server (監視src 檔案變化)

const {watch} = require ('gulp') , 監視檔案,判斷知否執行任務

watch('參數1:路徑',參數2: 任務)

開啟server前使用

const serve = ()=> {
    watch('src/assets/styles/*.scss', style)
    watch('src/assets/scripts/*.js', script)
    watch('src/*.html', page)
           

Gulp 案例: useref 

yarn add gulp-useref --dev

dist 中html 檔案内的檔案引用打包成vendor 

.pipe( useRef( {searchPath:['dist','.']} ) )

const useRef = require('gulp-useref');

 const ref = ()=>{
     return src('dist/*.html',{base:'dist'})
            .pipe(useRef({searchPath:['dist','.']}))
            .pipe(dest('dist'))
 }
           

Gulp 案例: 檔案壓縮(gulp-htmlmin,gulp-clean-css,gulp-uglify)

壓縮html,css, js

yarn add gulp-if --dev

compile 完成後 會生成<start> <end> 字元, 根據此來進行壓縮, 是以需要先compile 并在寫入dist 前完成 壓縮

const isif = require('gulp-if');
const htmlMin = require('gulp-htmlmin');
const cleanCss = require('gulp-clean-css');
const uglify = require('gulp-uglify');

 const ref = ()=>{
     return src('dist/*.html',{base:'dist'})
            .pipe(useRef({searchPath:['dist','.']}))
            .pipe(isif(/\.js$/, uglify()) )
            .pipe(isif(/\.css$/, cleanCss()) )
            .pipe(isif(/\.html$/, htmlmin({ 
                collapseWhitespace:true,
                minifyCSS:true,
                minifyJS:true,
            })) )
            .pipe(dest('release'))
 }
           

第九章: 可複用_自動化建構_工作流

使用腳手架建立新項目 => github 托管

複制 pulpfile.js 至 新項目入口檔案 lib/index.js

将源中的devDependencies複制到目标的dependencies 并在目标項目中安裝這些檔案 yarn

删除 源: gulpfile.js 内容, devDependencies 字元,node_modules檔案

将模版在全局link, yarn link

在源檔案中 yarn link <name>pages

在源檔案中有 scripts 字元, 運作他們,

關于.pipe(babel({ presets:[require('@babel/preset-env')] }))

require()會在自己目錄下找,一直向上級找。

yarn add gulp-cli --dev

yarn add gulp --dev

繼續閱讀