專案地址
簡介
- 公司有個新專案要做官網,需要支援國際化,UI設計了很多頁面,老闆著急要於是我們就直接用 html + css + jquery分工開發了, ,做出來的專案結構是這樣的(直接部署到伺服器上):
- 等到專案維護迭代的時候就很麻煩,遇到了很多問題:
- 每個html頁面都有導航、footer、head等公共頁面,修改需要設計所有檔案
- 沒有使用css前處理器,用慣了sass,css巢狀寫起來很彆扭
- 用慣了ES6,總是想寫let
- 資原始檔沒有加hash值
- 新加語言種類需要把所有html頁面複製一份重新編寫
- 正好最近看招聘資訊,好多要求要會用webpack和gulp,就想著學學gulp,用gulp搭個腳手架升級一下官網專案。
- 下面來介紹一下這個腳手架的搭建過程
專案介紹
專案簡介
- 專案基於gulp、babel7構建
- 使用ejs開發靜態頁面,支援國際化開發
- js支援commenjs規範以及esm規範
- 使用sass預處理css,使用postcss處理瀏覽器字尾
- 使用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"//生產環境
},
複製程式碼
開發與生產
區別
- 首先看一下生產環境與開發環境區別:
- 生產環境需要資源加hash值,防止使用者快取問題
- 生產環境需要壓縮程式碼
- 開發環境需要建立本地伺服器,處理轉發請求
- 開發環境檔案更改需要同步更新,重新整理瀏覽器
- 開發環境檔案需要新增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的處理,再來回顧一下我們的需求
- 導航、footer、head等可以使用公共模板
- 新增國際化配置檔案可以生成不同語言頁面
- 因此我使用了 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/資料夾中為不語言新增不同配置
- 舉個例子:
- 比如en.json以及zh-cn.json的配置如下
//en.json
{
"footer": "footer"
}
//zh-cn.json
{
"footer": "福特兒"
}
複製程式碼
- ejs中使用模板
<div><%= footer %></div>
複製程式碼
- 打包出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一邊搭的,很多處理方法都是一邊搜尋一邊找合適的加上的,希望大家多提意見。