讓我們用gulp寫個前端腳手架

正兒八經兒的阿怪發表於2019-03-04

專案地址

簡介

  • 公司有個新專案要做官網,需要支援國際化,UI設計了很多頁面,老闆著急要於是我們就直接用 html + css + jquery分工開發了, ,做出來的專案結構是這樣的(直接部署到伺服器上):

讓我們用gulp寫個前端腳手架

  • 等到專案維護迭代的時候就很麻煩,遇到了很多問題:
  1. 每個html頁面都有導航、footer、head等公共頁面,修改需要設計所有檔案
  2. 沒有使用css前處理器,用慣了sass,css巢狀寫起來很彆扭
  3. 用慣了ES6,總是想寫let
  4. 資原始檔沒有加hash值
  5. 新加語言種類需要把所有html頁面複製一份重新編寫
  • 正好最近看招聘資訊,好多要求要會用webpack和gulp,就想著學學gulp,用gulp搭個腳手架升級一下官網專案。
  • 下面來介紹一下這個腳手架的搭建過程

專案介紹

專案簡介

  1. 專案基於gulp、babel7構建
  2. 使用ejs開發靜態頁面,支援國際化開發
  3. js支援commenjs規範以及esm規範
  4. 使用sass預處理css,使用postcss處理瀏覽器字尾
  5. 使用browser-sync構建開發環境,使用http-proxy-middleware處理請求代理

專案結構

├── README.md
├── dev     //開發環境打包程式碼
├── dist    //生產環境打包程式碼
├── favicon.ico
├── gulp    //gulp配置
├── gulpfile.babel.js   //babel配置
├── package.json
├── src
│   ├── html    //index入口檔案
│   │   ├── ejs
│   │   │   └── footer.ejs
│   │   └── index.html
│   ├── imgs    //圖片
│   │   └── 123.jpeg
│   ├── js  //js
│   │   ├── index.js
│   │   └── moduleA.js
│   ├── lang    //國際化語言檔案
│   │   ├── en.json
│   │   └── zh-cn.json
│   ├── scss    //sass檔案
│   │   ├── common
│   │   │   └── _reset.scss
│   │   └── index.scss
│   └── static  //靜態檔案
│       └── jquery-3.2.1.min.js
└── user.config.js.config   //開發配置檔案
複製程式碼

使用gulp打包

gulp簡介

  • gulp相關內容可以檢視官網以及其它文章
  • gulp配置檔案中使用相關庫可以查詢github瞭解功能以及使用方法

gulp配置

  • gulp主要任務配置都放在了gulp資料夾下
  • 根目錄新建了gulpfile.babel.js檔案,並使用 require-dir 匯入gulp資料夾下的任務
//gulpfile.babel.js檔案
import requireDir from 'require-dir'
requireDir('./gulp/task')
requireDir('./gulp')
複製程式碼
  • 專案分為開發環境和生產環境兩個主要任務,/gulp/dev.js是開發環境任務,/gulp/prod.js是生產環境任務,/gulp/task/資料夾下是其他任務

  • npm 命令

"scripts": {
    "start": "npm run dev",//開發環境
    "dev": "gulp dev",
    "build": "gulp prod"//生產環境
},
複製程式碼

開發與生產

區別

  • 首先看一下生產環境與開發環境區別:
  1. 生產環境需要資源加hash值,防止使用者快取問題
  2. 生產環境需要壓縮程式碼
  3. 開發環境需要建立本地伺服器,處理轉發請求
  4. 開發環境檔案更改需要同步更新,重新整理瀏覽器
  5. 開發環境檔案需要新增sourcemap配置,方便檢查錯誤

開發環境

  • gulp dev
gulp.task('dev',
    gulp.series(
        'clean:dev',
        'html:dev',
        gulp.parallel('scss:dev', 'js:dev', 'static:dev', 'favicon:dev'),
        'img:dev',
        'server'
    ))
複製程式碼
  • 開發環境使用 browser-sync 搭建伺服器,使用 http-proxy-middleware 代理請求

  • 同時建立user.config.js.config檔案,供開發配置服務埠和請求代理配置使用,需要複製一份改為user.config.js,防止多人開發衝突。

  • 開發環境不進行程式碼壓縮等處理,打包後程式碼放在dev資料夾下

  • /gulp/task/server.js檔案中建立server任務

gulp.task('server', function () {
    browserSync.init({
        server: "./dev",
        port: userConfig.port,
        middleware: proxyMiddleware
    });
});
複製程式碼

生產環境

  • gulp prod
gulp.task('prod',
    gulp.series(
        'clean',
        'html:prod',
        gulp.parallel('scss:prod', 'js:prod', 'static:prod', 'favicon:dist'),
        'img:prod'
    ))
複製程式碼
  • 生產環境任務主要是在開發環境任務基礎上,新增壓縮、hash編碼等任務,打包檔案放在dist資料夾下

檔案處理

清理資料夾

  • 打包前使用 gulp-clean 清空dev|dist資料夾,/gulp/task/clean.js檔案中的clean:prod、clean:dev、clean任務
gulp.task('clean:prod',function () {
    return gulp.src('./dist', {read: false,allowEmpty: true})
        .pipe(gulpClean());
})
gulp.task('clean:dev',function () {
    return gulp.src('./dev', {read: false,allowEmpty: true})
        .pipe(gulpClean());
})
gulp.task('clean',gulp.parallel('clean:prod','clean:dev'))
複製程式碼

處理js

  • 由於官網專案不需要太多js操作,因此引入jquery足夠了,jquery作為靜態檔案引入,之後會講
  • /gulp/task/js.js檔案處理js任務,專案js入口程式碼在/src/js/資料夾下
  • 首先分析一下需求,因為有多個html頁面,每個頁面需要處理不同的表單和頁面邏輯,因此js也需要有多個入口。
  • html中可以使用相對於伺服器路徑引用js檔案
<script src="/js/index.js"></script>
複製程式碼
  • 使用 browserify 打包js檔案,可以支援commonjs模組化,同時也使用了 gulp-babel ,使專案支援SE6語法以及esm模組化開發。
  • 入口檔案配置
//如果需要多個入口檔案,則繼續配置
let entries = [
    {
        name: 'index',
        entry: ['src/js/index.js']
    }
]
複製程式碼
  • 開發環境處理js,處理流程:任務js:dev->devArrFun迴圈入口檔案->makeBundle打包js->重新命名->生成檔案到dev資料夾->同時監聽變化->重新打包重新整理瀏覽器
  • 生產環境處理js,處理流程:任務js:prod->prodArrFun迴圈入口檔案->bundle打包js儲存在dev資料夾中->任務js:dev2dist壓縮js、新增hash、替換html檔案中的路徑
  • 任務程式碼
//使用browserify和babel打包js檔案
function makeBundle(name,entry){
    if(!bundleArr[name]){
        let b = browserify({
            entries: entry,
            debug: devServer,
            extensions: ['es6'],
        })
            .transform(html2js)
            .transform(babelify)
            .on('error', function (err) { console.error(err); })
        bundleArr[name] = b
    }
    return bundleArr[name]
}

//直接打包不檢測更新
function bundle(name,entry){
    let b = makeBundle(name,entry)
    return b
        .bundle()
        .pipe(source(`${name}.js`))
        .pipe(buffer())
        .pipe(replace('@img', 'img'))
        .pipe(gulp.dest('dev/js'))
        .pipe(gulpif(devServer,global.browserSync.reload({stream: true})))//檔案變化重新整理瀏覽器
}

//開發環境打包
let devArrFun = entries.map(i=>{//迴圈入口,每個檔案都打包
    return devFun.bind(null,i.name,i.entry)
})

//打包js並檢測更新
function devFun(name,entry) {

    devServer = true

    let b = makeBundle(name,entry)

    b.plugin(watchify);

    //檔案變化重新打包js
    b.on('update',bundle.bind(null,name,entry))

    return bundle(name,entry)
}

//生產環境打包
let prodArrFun = entries.map(i=>{
    return bundle.bind(null,i.name,i.entry)
})

gulp.task('js',gulp.parallel(prodArrFun))

//開發環境任務
gulp.task('js:dev',gulp.parallel(devArrFun))

//將dev中檔案轉入dist資料夾中
gulp.task('js:dev2dist',function () {
    return gulp.src('dev/js/*.js')
        .pipe(uglify())
        .pipe(md5(6, './dist/*.html'))
        .pipe(gulp.dest('dist/js'))
})
//生產環境任務
gulp.task('js:prod',gulp.series('js','js:dev2dist'))
複製程式碼

處理css

  • 使用sass預處理css,使用postcss的autoprefixer新增瀏覽器字首,/src/scss/資料夾放置sass入口檔案
  • 使用相對於伺服器路徑引用css檔案
<link rel="stylesheet" href="/css/index.css">
複製程式碼
  • 處理流程依然是查詢入口檔案,打包scss檔案,同時開發環境監聽檔案變化重新整理瀏覽器,生產環境進一步處理開發環境打包的檔案。
  • 任務程式碼
function scss() {
    return gulp
        .src('./src/scss/*.scss')//查詢入口檔案
        .pipe(gulpif(devServer,sourcemaps.init()))//開發環境新增sourcemap配置
        .pipe(sass().on('error', sass.logError))
        .pipe(postcss([autoprefixer()]))//新增瀏覽器字首
        .pipe(replace('../imgs', '../imgs'))//處理圖片路徑
        .pipe(replace('../../imgs', '../imgs'))
        .pipe(gulpif(devServer,sourcemaps.write()))
        .pipe(gulp.dest('./dev/css'));//開發環境存放檔案
}

gulp.task('scss',scss)

gulp.task('scss:dev', function () {
    devServer = true
    //開發環境監聽檔案變化重新打包並重新整理瀏覽器
    gulp.watch(['./src/scss/*.scss','./src/scss/*/*.*'], function (event) {
        return scss().pipe(global.browserSync.reload({stream: true}));
    });
    return scss()
});

gulp.task('scss:dev2dist',function () {
    return gulp.src('./dev/css/*.css')
        .pipe(webpcss())//處理webp檔案
        .pipe(cleanCSS())//壓縮檔案
        .pipe(md5(6, './dist/*.html'))//新增hash,並替換html中的檔名稱
        .pipe(gulp.dest('./dist/css'));//生產環境儲存檔案
})

gulp.task('scss:prod', gulp.series('scss','scss:dev2dist'));
複製程式碼

處理圖片

  • css以及html中使用圖片可以直接相對路徑引用,在scss以及html打包中會替換img檔案路徑
  • 圖片儲存在/src/imgs/資料夾中,目前只支援兩級目錄
  • 開發環境只是圖片複製,生產環境會壓縮圖片、新增hash、替換css\html中的檔案
  • 任務程式碼
const srcArr = ['./src/imgs/*.{png,gif,jpg,jpeg}','./src/imgs/*/*.{png,gif,jpg,jpeg}']

function img(){
    return gulp
        .src(srcArr)
        .pipe(gulp.dest('./dev/imgs'))
}

gulp.task('img',img)

gulp.task('img:dev',function () {

    gulp.watch(srcArr, function (event) {
        return img(event.path)
            .pipe(global.browserSync.reload({stream: true}))
    });

    return img();
})

gulp.task('img:dev2dist',function () {
    return gulp
        .src(srcArr)
        .pipe(imagemin())//壓縮圖片
        .pipe(md5(6, ['./dist/*.html', './dist/css/*.css', './dist/js/*.js']))//新增hash,替換檔名
        .pipe(gulp.dest('./dist/imgs'))
})

gulp.task('img:prod',gulp.series('img','img:dev2dist'))
複製程式碼

處理靜態檔案

  • jquery等其他庫可以放在/src/static/檔案加下,在html中使用相對於伺服器路徑引用即可,/gulp/task/static.js中的任務負責,處理靜態資源的複製。
<script src="/static/jquery-3.2.1.min.js"></script>
複製程式碼

ejs以及國際化配置

  • 最後一項是對於html的處理,再來回顧一下我們的需求
  1. 導航、footer、head等可以使用公共模板
  2. 新增國際化配置檔案可以生成不同語言頁面
  • 因此我使用了 ejs 來編寫html,同時新增語言配置的json檔案,打包出不同的語言頁面

處理公共html模組

  • /src/html/資料夾存放html以及ejs檔案,使用ejs載入公共模組
<%- include('ejs/footer',{type: common.type}) %>
複製程式碼

html入口

  • 處理html的任務在/task/html.js中,每次新增頁面,需要配置html入口檔案
//該配置會打包出index.html(中文)以及index-en.html(英文)兩個頁面
//html入口檔案
let htmlConfig = [
    {
        entry: 'src/html/index.html',
        name: 'index.html',//打包後檔名
        lang: 'zh-cn'//ejs語言配置檔案,對應/src/lang下的json檔案
    },
    {
        entry: 'src/html/index.html',
        name: 'index-en.html',
        lang: 'en'
    },
]
複製程式碼

國際化處理

  • /src/lang/資料夾中為不語言新增不同配置
  • 舉個例子:
  1. 比如en.json以及zh-cn.json的配置如下
//en.json
{
    "footer": "footer"
}
//zh-cn.json
{
    "footer": "福特兒"
}
複製程式碼
  1. ejs中使用模板
<div><%= footer %></div>
複製程式碼
  1. 打包出html分別為
//index.html
<div>福特兒</div>
//index-en.html
<div>footer</div>
複製程式碼
  • 我們還可使用ejs其他語法編寫比如:按條件渲染生成不同html處理頁面差異、用變數處理dom元素屬性、生成不同class處理語言顯示等問題,這些就需要靠你的智慧了。

  • 任務程式碼

//處理ejs模板
function html(config){
    return gulp
        .src(config.entry)
        .pipe(data(function(file) {//載入語言配置
            return JSON.parse(fs.readFileSync(`src/lang/${config.lang}.json`))
        }))
        .pipe(replace('../imgs', './imgs'))//替換圖片路徑
        .pipe(replace('../../imgs', './imgs'))
        .pipe(ejs().on('error', handleError))//錯誤處理
        .pipe(rename(config.name))//替換檔名
        .pipe(gulp.dest('dev'))
}

let htmlDevArr = htmlConfig.map(config=>{
    return function (config) {
        //監聽變化重新打包並且重新整理瀏覽器
        gulp.watch([config.entry,'src/html/*/*.*','src/lang/*'], function (event) {
            return html(config).pipe(global.browserSync.reload({stream: true}));
        });
        return html(config)
    }.bind(null,config)
})

//開發環境命令
gulp.task('html:dev',gulp.parallel(htmlDevArr))

gulp.task('html:dev2dist',function () {
    return gulp
        .src('dev/*.html')
        .pipe(htmlmin({ collapseWhitespace: true }))//壓縮html
        .pipe(gulp.dest('dist'))
})

let htmlProdArr = htmlConfig.map(config=>{
    return function (config) {
        return html(config)
    }.bind(null,config)
})
//生產環境命令
gulp.task('html:prod',gulp.series(gulp.parallel(htmlProdArr),'html:dev2dist'))
複製程式碼

錯誤處理

  • 開發中還遇到了一個問題:ejs模板報錯時會停止gulp任務,需要使用 gulp-notify 處理報錯,防止任務終止
const notify = require("gulp-notify");
module.exports = function(){
    var args = Array.prototype.slice.call(arguments)

    notify.onError({
        title: 'compile error',
        message: '<%=error.message %>'
    }).apply(this, args)

    this.emit();
}
複製程式碼

總結

  • 以上就是gulp官網專案腳手架搭建過程,由於是一邊學習gulp一邊搭的,很多處理方法都是一邊搜尋一邊找合適的加上的,希望大家多提意見。

相關文章