- 公司有个新项目要做官网,需要支持国际化,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一边搭的,很多处理方法都是一边搜索一边找合适的加上的,希望大家多提意见。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
The Smashing Book
Jacob Gube、Dmitry Fadeev、Chris Spooner、Darius A Monsef IV、Alessandro Cattaneo、Steven Snell、David Leggett、Andrew Maier、Kayla Knight、Yves Peters、René Schmidt、Smashing Magazine editorial team、Vitaly Friedman、Sven Lennartz / 2009 / $ 29.90 / € 23.90
The Smashing Book is a printed book about best practices in modern Web design. The book shares technical tips and best practices on coding, usability and optimization and explores how to create succes......一起来看看 《The Smashing Book》 这本书的介绍吧!