项目地址
在这里! https://github.com/crazyaguai/gulp-website
简介
公司有个新项目要做官网, 需要支持国际化, UI 设计了很多页面, 老板着急要于是我们就直接用 html + CSS + jQuery 分工开发了, , 做出来的项目结构是这样的 (直接部署到服务器上):
等到项目维护迭代的时候就很麻烦, 遇到了很多问题:
每个 HTML 页面都有导航, footer,head 等公共页面, 修改需要设计所有文件
没有使用 CSS 预处理器, 用惯了 Sass,CSS 嵌套写起来很别扭
用惯了 ES6, 总是想写 let
资源文件没有加 hash 值
新加语言种类需要把所有 HTML 页面复制一份重新编写
正好最近看招聘信息, 好多要求要会用 webpack 和 gulp, 就想着学学 gulp, 用 gulp 搭个脚手架升级一下官网项目.
下面来介绍一下这个脚手架的搭建过程
项目介绍
项目简介
项目基于 gulp,babel7 构建
使用 https://ejs.bootcss.com/ 开发静态页面, 支持国际化开发
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 https://github.com/aseemk/requireDir 导入 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 https://www.browsersync.io/docs 搭建服务器, 使用 https://github.com/chimurai/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 文件夹下
文件处理
清理文件夹
打包前使用 https://github.com/peter-vilja/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>
使用 https://github.com/browserify/browserify 打包 JS 文件, 可以支持 commonjs 模块化, 同时也使用了 https://github.com/babel/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 等可以使用公共模板
添加国际化配置文件可以生成不同语言页面
因此我使用了 https://ejs.bootcss.com/ 来编写 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 任务, 需要使用 https://github.com/mikaelbr/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 一边搭的, 很多处理方法都是一边搜索一边找合适的加上的, 希望大家多提意见.
来源: https://juejin.im/post/5c7d20545188251bb13baea6