前言
- 项目背景:
- 项目存在于n+1年前的php旧框架中,其中使用的技术大家不用猜应该也知道了;
- 由于其中许多业务一直都存在这个php旧框架中,而且暂时性的不存在做整体迁移的情况,所以只能在php旧框架中动手了 [? 伤心太平洋];
- 现阶段需解决的点:
- php旧框架版本戳问题 [? 暗自伤心]:由于浏览器缓存机制,每次修改文件,需手动更改对应的版本戳,修改成本较高,涉及文件变更也较多;
- ES6语法支持问题 [? 猛男落泪]:了解过
javascript
的同学应该知道,ES6语法可以更精简你的代码,从而提升你的开发效率; - js、css 文件未压缩 [? 用户难受];
- 由于我们都是有追求技术的Team,同时也秉承用户至上的理念,所以为了解决上述问题,我们的技术革命刻不容缓;
技术调研
- Grunt VS Gulp VS Webapck
- Grunt:
- 配置与生态没有达到我们的要求
- Webapck:
- 不支持CMD模式,需下载对应的转换成AMD或CommonJs的插件;
- 由于项目的seajs写法没有统一性,故可能存在转换后踩坑的情况;
- Gulp:
- 与seajs的生态配合较好;
- 针对seajs打包的一系列流程与插件都有实例;
- 与其它项目的打包构建一致性;
- 终上所述,我们选择了gulp [? ]
php旧框架现有目录介绍
└── 项目名
├── .... php文件
├── images 静态图片
├── include 前端页面静态js、css、images等(gulp运行后主要操作的目录一)
│ ├── ...
│ ├── javascript
│ ├── ...
│ ├── css
│ ├── ...
│ ├── static
│ ├── ...
│ ├── gulpfile.js
│ ├── package.json
│ └── ...
├── lib php文件
├── ... 其它php文件
├── tmpl 前端页面模板文件(gulp运行后主要操作的目录二)
└── ... 其它配置文件
Gulp打包流程图
gulp打包依赖
-
gulp:不多讲
-
gulp-uglify:js文件压缩
-
gulp-minify-css:css压缩
-
gulp-fs-cache:文件缓存插件
-
gulp-babel:js文件转成es5语法插件
-
gulp-replace:字符串替换插件
-
pump:小型节点模块,可将stream流连接在一起并在其中一个关闭时将其全部销毁
-
run-sequence:控制多个任务进行顺序执行或者并行执行
-
gulp-591-rev:自定义包 类似:gulp-rev 插件生成的
manifest.json
文件中的映射 -
gulp-591-collect:自定义包 类似:gulp-rev-collector 替换文件名称, 也可以替换路径
-
gulp-combo-seajs:自定义包 类似:gulp-seajs-combo seajs合并包主要解决现有插件的不能满足的问题
-
gulp-convert-encoding:文件编码转码
gulp运行
由于前端资源与模板主要存在于include目录与tmpl目录,故只会对该两个目录进行操作;
1. 操作include目录:
该文件目录主要保存的是js、css、images静态资源
1.1 复制include目录下的文件
- 主要是为了区分php的变量访问,防止出现访问错误;
on('end', fun)
:主要为了区分gulp异步打包机制,防止某些文件复制了被替换的现象;
// 复制include文件下的某些文件至.build,去做构建处理
gulp.task('copyInclude', function (cb) {
gulp.src(copyPaths.paths) // paths为路径处理
.pipe(gulp.dest('.build'))
.on('end',function() {
console.log('copyInclude 执行完成了');
cb();
})
});
1.2 压缩合并js, css文件
-
压缩
css
的同时,转义css
文件中的相对路径引用,例如:../../../../../
// 处理并压缩css文件; gulp.task('minifyCSS', function () { minifyCSS('{css,javascript,static}/**/*.css', '.build') }) // ...... /** * 壓縮 CSS 公共方法 * replace(/(..\/){3,5}images\//gi’替换图片路径的正則 * 匹配 ../../../images/ 字符串內容,../ 是 3~5 個 * @param {String} path 目標路徑 * @param {String} destPath 輸出路徑 */ function minifyCSS (path, destPath) { var cssFsCache = fsCache('.gulpCache/csscache'); gulp.src(path) .pipe(replace(/(..\/){3,5}images\//gi, `//images${env_name}.591.com.tw/`)) .pipe(cssFsCache) .pipe(minifycss({ compatibility: 'ie9' })) .pipe(cssFsCache.restore) .pipe(gulp.dest(destPath)); }
-
压缩
js
文件同时,做babel
的es6
语法的转义;// 处理并压缩 JS 文件 gulp.task('uglifyJS', ['seaCombo'], function (cb) { jsPaths.paths.forEach(function(item,index){ // index === (jsPaths.paths.length - 1) 解决回调函数次数过多问题,只能回调一次 uglifyJS(item.entryPath, item.outputPath, index === (jsPaths.paths.length - 1) && cb) }) }); /** * JS 壓縮 * @param {String} path * @param {String} destPath * @param {回調函數} cb */ function uglifyJS (path, destPath, cb) { var jsFsCache = fsCache('.gulpCache/jscache'); pump([ gulp.src(path), jsFsCache, babel({ presets: ['@babel/env'], sourceType: 'script' // 簡單理解,不會添加 'use strict' }), uglify({ mangle: { reserved: ['require', 'exports', 'module'] } }), jsFsCache.restore, gulp.dest(destPath) ], cb) }
-
压缩文件时忽略
require
,exports
,module
是为了seajs合并时做处理; -
seaCombo
任务合并seajs:// seajs 的 alias config const staticConfig = require('./static/alias/static'); const houseConfig = require('./static/alias/house'); const userCenterConfig = require('./static/alias/userCenter'); const configAlias = Object.assign({}, staticConfig, userCenterConfig, houseConfig); // 合併 sea.config 中的alias 的 JS gulp.task('seaCombo', function (cb) { const configAliasArr = Object.keys(configAlias); configAliasArr.forEach(function (item, index) { // 切割路径有 ? 的时间戳, 为了使seajsCombo插件识别 let nowItem = configAlias[item].split('?')[0]; // 从相对路径变成绝对路径,为了使seajsCombo插件识别 configAlias[item] = path.resolve(__dirname, nowItem) let srcString = configAlias[item]; let _base; const hasStatic = srcString.includes('static'); const hasJavascript = srcString.includes('javascript'); if (hasStatic || hasJavascript) { _base = (hasStatic && 'static') || (hasJavascript && 'javascript') } else { return; } return gulp.src(srcString, { base: _base }) .pipe(seajsCombo({ map: configAlias, ignore: jsPaths.ignoreConfig })) .pipe(gulp.dest('.build/' + _base)) .on('end',function() { index === (configAliasArr.length - 1) && cb(); }) }) });
-
自定义插件
gulp-combo-seajs
主要解决问题:- 支持
require("xx.css")
引入问题 - 以
seajs.config
的alias别名设置文件名命名模块id,而不是以当前文件名命名模块id; - 在部分文件不能识别成正确的
.js
文件,导致会在xx.js
文件后面加上.js
成xx.js.js
;
- 支持
-
1.3 生成js, css文件的hash映射文件
-
生成hash映射主要为了区分版本戳
//js生成文件hash编码并生成 rev-manifest.json文件名对照映射 gulp.task('revJson', function(){ return gulp.src(['.build/**/*.{js,css}']) .pipe(rev()) .pipe(rev.manifest('rev-manifest.json')) .pipe(gulp.dest('.build')); });
1.4 更改需要映射的js文件的版本戳(主要针对seajs中的配置)
-
针对seajs中的配置文件路径添加版本戳
// 更改需要映射的js文件的版本戳 gulp.task('revJs', function (cb) { jsPaths.seaConfig.forEach(function (item, index) { return gulp.src(['.build/rev-manifest.json'].concat(item.entryPath)) .pipe(revCollector({ replaceReved: true })) .pipe(gulp.dest(item.outputPath)) .on('end',function() { if (index === (jsPaths.seaConfig.length - 1) ) { console.log( 'revJs 执行完成了' ) cb(); }; }) }) })
1.5 複製.build目录文件到 build 目錄
// 複製.build目录文件到 build 目錄
gulp.task('copyBuild', function () {
return gulp.src('.build/**/*.*')
.pipe(gulp.dest('build'));
});
2. 操作tmpl目录:
- 该文件目录主要保存的是页面模板文件,包括php和html模板
2.1 复制tmpl模板中的文件 至 tmpl_gulp 文件中
// 复制tmpl模板中的文件 至 tmpl_gulp 文件中
gulp.task('copyTmpl', function () {
// 1、先忽略需要更改的文件模板,以免发生覆盖
let nowTmpls = ignorePaths(tmplPaths)
// 2、再进行copy
return gulp.src(['../tmpl/**/*.*'].concat(nowTmpls))
.pipe(gulp.dest('../tmpl_gulp'));
});
2.2 更改tmpl內对应模板文件的CSS,JS版本戳
gulp.task('revHtml', function () {
buildSlicePaths(tmplPaths, function (path, destPath) {
return gulp.src(['.build/rev-manifest.json', path])
// 1.先把文件直接转成utf-8格式
.pipe(convertEncoding({
from: 'GBK',
to: 'utf8'
}))
// 2.再去映射修改的文件
.pipe(revCollector({
replaceReved: true
}))
// 3.再通过utf-8转换成原来的编码格式
.pipe(convertEncoding({
from: 'utf8',
to: 'GBK'
}))
// 4.最后生成文件模板
.pipe(gulp.dest('../tmpl_gulp/' + destPath));
})
})
打包后目录
完整gulpfile.js配置
/**
* @description: 舊框架自動構建配置 - 基於 gulp
* @author: https://github.com/jeddygong
* @dateTime: 2021-02-29 14:45:02
*/
// 引入 gulp及组件
var gulp = require('gulp'),
uglify = require('gulp-uglify'),
fsCache = require('gulp-fs-cache'),
pump = require('pump'),
babel = require('gulp-babel'),
replace = require('gulp-replace'),
runSequence = require('run-sequence')
minifycss = require('gulp-minify-css');
const path = require('path');
const rev = require('gulp-591-rev');
const revCollector = require('gulp-591-collect');
const seajsCombo = require('gulp-combo-seajs');
const convertEncoding = require("gulp-convert-encoding");
// seajs 的 alias config
const staticConfig = require('./static/alias/static');
const houseConfig = require('./static/alias/house');
const userCenterConfig = require('./static/alias/userCenter');
const configAlias = Object.assign({}, staticConfig, userCenterConfig, houseConfig);
// 當前環境變量
const env = process.env.NODE_ENV
// 環境變量判斷
const env_name = (env === 'develop' ? '.debug' : '')
// 需要copy的include目录下的文件
const copyPaths = {
paths: [
'{ajax,bootstrap,css,javascript,static}/**/*.*',
]
}
// 需要映射的 php 或 html 文件中的版本戳 文件,添加到paths中即可
const tmplPaths = {
base: '../tmpl/',
paths: [
'index/house/postRent/index.tpl.php'
]
}
// js路径配置
const jsPaths = {
// 需要添加合并的seajs文件, 只针对页面是使用include/javascript/public/config.js的配置
manifestPaths: [
'.build/javascript/newUserCenter/newMedium/index.js'
],
// 基本js文件路径,需要压缩编译的文件
paths: [
// javascript 路径下的js文件暂不压缩,要不然会有问题,例子javascript/housing/newList.js
{
entryPath: [
'.build/javascript/**/*.js',
'!.build/javascript/mobile/preloadData.js',
'!.build/javascript/house/list.js',
'!.build/javascript/seajs/**/*.*',
'!.build/javascript/plugin/*.*',
'!.build/javascript/newPlugin/*.*'
],
outputPath: '.build/javascript'
},
{
entryPath: ['.build/static/**/*.js'],
outputPath: '.build/static'
}
],
// 需要合并添加版本戳替换seajs配置
seaConfig: [
{
entryPath: ['.build/static/alias/static.js', '.build/static/alias/userCenter.js', '.build/static/alias/house.js'],
outputPath: '.build/static/alias'
},
{
entryPath: ['.build/javascript/public/config.js'],
outputPath: '.build/javascript/public'
},
{
entryPath: ['.build/javascript/newPublic/dsConfig.js'],
outputPath: '.build/javascript/newPublic'
}
],
ignoreConfig: [
'/include/javascript/jquery.validate.js',
...Object.keys(staticConfig)
]
}
// 复制include文件下的某些文件至.build,去做构建处理
gulp.task('copyInclude', function (cb) {
gulp.src(copyPaths.paths)
.pipe(gulp.dest('.build'))
.on('end',function() {
console.log('copyInclude 执行完成了');
cb();
})
});
// 处理并压缩css文件;
gulp.task('minifyCSS', function () {
minifyCSS('{css,javascript,static}/**/*.css', '.build')
})
// 处理并压缩 JS 文件
gulp.task('uglifyJS', ['seaCombo'], function (cb) {
jsPaths.paths.forEach(function(item,index){
// index === (jsPaths.paths.length - 1) 解决回调函数次数过多问题,只能回调一次
uglifyJS(item.entryPath, item.outputPath, index === (jsPaths.paths.length - 1) && cb)
})
});
// 合併 sea.config 中的alias 的 JS
gulp.task('seaCombo', function (cb) {
const configAliasArr = Object.keys(configAlias);
configAliasArr.forEach(function (item, index) {
// 切割路径有 ? 的时间戳
let nowItem = configAlias[item].split('?')[0];
// 从相对路径变成绝对路径
configAlias[item] = path.resolve(__dirname, nowItem)
let srcString = configAlias[item];
let _base;
const hasStatic = srcString.includes('static');
const hasJavascript = srcString.includes('javascript');
if (hasStatic || hasJavascript) {
_base = (hasStatic && 'static') || (hasJavascript && 'javascript')
} else {
return;
}
return gulp.src(srcString, { base: _base })
.pipe(seajsCombo({
map: configAlias,
ignore: jsPaths.ignoreConfig
}))
.pipe(gulp.dest('.build/' + _base))
.on('end',function() {
// console.log( index === (configAliasArr.length - 1) && 'seaCombo 执行完成了' );
index === (configAliasArr.length - 1) && cb();
})
})
});
//js生成文件hash编码并生成 rev-manifest.json文件名对照映射
gulp.task('revJson', function(){
return gulp.src(['.build/**/*.{js,css}'])
.pipe(rev())
.pipe(rev.manifest('rev-manifest.json'))
.pipe(gulp.dest('.build'));
});
// 更改需要映射的js文件的版本戳
gulp.task('revJs', function (cb) {
jsPaths.seaConfig.forEach(function (item, index) {
return gulp.src(['.build/rev-manifest.json'].concat(item.entryPath))
.pipe(revCollector({
replaceReved: true
}))
.pipe(gulp.dest(item.outputPath))
.on('end',function() {
if (index === (jsPaths.seaConfig.length - 1) ) {
console.log( 'revJs 执行完成了' )
cb();
};
})
})
})
// 複製.build目录文件到 build 目錄
gulp.task('copyBuild', function () {
return gulp.src('.build/**/*.*')
.pipe(gulp.dest('build'));
});
// 复制tmpl模板中的文件 至 tmpl_gulp 文件中
gulp.task('copyTmpl', function () {
// 1、先忽略需要更改的文件模板,以免发生覆盖
let nowTmpls = ignorePaths(tmplPaths)
// 2、再进行copy
return gulp.src(['../tmpl/**/*.*'].concat(nowTmpls))
.pipe(gulp.dest('../tmpl_gulp'));
});
// 更改tmpl內对应模板文件的CSS,JS版本戳
gulp.task('revHtml', function () {
buildSlicePaths(tmplPaths, function (path, destPath) {
return gulp.src(['.build/rev-manifest.json', path])
// 1.先把文件直接转成utf-8格式
.pipe(convertEncoding({
from: 'GBK',
to: 'utf8'
}))
// 2.再去映射修改的文件
.pipe(revCollector({
replaceReved: true
}))
// 3.再通过utf-8转换成原来的编码格式
.pipe(convertEncoding({
from: 'utf8',
to: 'GBK'
}))
// 4.最后生成文件模板
.pipe(gulp.dest('../tmpl_gulp/' + destPath));
})
})
// 默认任务 清空图片、样式、js并重建 运行语句 gulp
gulp.task('default', ['start']);
gulp.task('start', function () {
console.log('/********************************', new Date().toLocaleString(), '*******************************************/');
runSequence(
['copyInclude'],
['uglifyJS', 'minifyCSS'],
['revJson'],
['revJs'],
/**
* 由于更改了revJs任务 jsPaths.seaConfig文件中的版本戳 并且是在revJson之后修改的
* 导致更改后的rev-manifest.json没有更新到,所以需要再次生成版本戳
*/
['revJson'],
['copyBuild'],
['copyTmpl'],
['revHtml']
);
});
/**
* JS 壓縮
* @param {String} path
* @param {String} destPath
* @param {回調函數} cb
*/
function uglifyJS (path, destPath, cb) {
var jsFsCache = fsCache('.gulpCache/jscache');
pump([
gulp.src(path),
jsFsCache,
babel({
presets: ['@babel/env'],
sourceType: 'script' // 簡單理解,不會添加 'use strict'
}),
uglify({
mangle: { reserved: ['require', 'exports', 'module'] }
}),
jsFsCache.restore,
gulp.dest(destPath)
],
cb)
}
/**
* 壓縮 CSS 公共方法
* replace(/(..\/){3,5}images\//gi’替换图片路径的正則
* 匹配 ../../../images/ 字符串內容,../ 是 3~5 個
* @param {String} path 目標路徑
* @param {String} destPath 輸出路徑
*/
function minifyCSS (path, destPath) {
var cssFsCache = fsCache('.gulpCache/csscache');
gulp.src(path)
.pipe(replace(/(..\/){3,5}images\//gi, `//images${env_name}.591.com.tw/`))
.pipe(cssFsCache)
.pipe(minifycss({
compatibility: 'ie9'
}))
.pipe(cssFsCache.restore)
.pipe(gulp.dest(destPath));
}
/**
* @description 需要忽略的文件不被copy,解决gulp的task任务异步问题
* @param {Array} manifestPaths 所有忽略的文件路径
* @return {Array}
*/
function ignorePaths (manifestPaths, base) {
return manifestPaths.paths.map(function(item,index){
return '!'+ manifestPaths.base + item;
})
}
/**
* @description 需要打包的文件路径做切割
* @Example 'javascript/newUserCenter/newMedium/index.js' => 'javascript/newUserCenter/newMedium' 和 'index.js'
* @param {Array} manifestPaths 所有忽略的文件路径
* @param {Function} cb
*/
function buildSlicePaths (manifestPaths, cb) {
manifestPaths.paths.forEach(function(item,index){
let name = item.split('/').length - 1;
// 查找最后一个/的位置
let sub = item.lastIndexOf('\/');
let destPath = '';
if (sub) {
destPath = item.slice(0, sub);
}
cb(manifestPaths.base + item, destPath, index);
})
}
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!