项目地址
简介
- 公司有个新项目要做官网,需要支持国际化,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一边搭的,很多处理方法都是一边搜索一边找合适的加上的,希望大家多提意见。