背景🚗🚕🚙
接手了一个前端项目,是用的nunjucks模版来做的,其余技术皆为纯原生。
项目遇到的问题
纯原生的项目虽然可扩展性极高,但由于之前公司对于web的网页并没有要求很高的性能,我的前辈大哥在构建项目的时候并没有对性能方面做太多考虑,这个项目要求必须有很高的模块可操作性,所以选用nunjucks模版来进行模块化渲染html,再加上此页面要用上很多的服务端数据计算来生成,所以就导致了两个问题👉👉👉:一个页面的加载要请求至少七个html文件;一个页面的生成至少调用8个左右的js文件进行计算。
为什么不用webpack等前端打包工具
当然是尝试使用过了,但在打包到js用nunjucks模版加载html文件的时候失败了,尝试了N种方法,实在是打包不出来,若有大佬知道,还望告诉老妹!!!😎
所以为什么要优化
公司在发展,客户在提要求,当遇到一个客户像批改论文给我的网页提需求的时候😟😟😟,我想每个小前端都很崩溃吧!对就是这样我走上了对这个项目的漫漫优化之路🥹🥹🥹
优化之路
面临的问题:
此项目最大的问题之一就是文件加载过多,其二就是涉及到一张高清图片的计算,必须等高清图片加载完成计算之后再打开,其三是该项目一个文件承载了很多种页面的可能性,之前没有对三方库进行加载及加载顺序的管理,导致所有三方库无论有没有用都被加载了。所以导致页面打开极慢!问题找到了就好解决了!🌈🌈🌈
计划:
1、改造目前所有的计算js文件,改为函数单一返回模式。
2、改造所有html模块文件,使用nunjucks模版提供的函数来控制展示。
3、将高清图片的加载放在异步加载中,同时改变此模块对应的html处理逻辑。
4、建立公共加载模块,分类对三方库进行加载管理,控制是否加载及加载顺序。
5、建立脚本.sh文件,使用cat和uglifyjs,对所有计算js文件及html文件进行打包压缩。
实施:
第一步:
所有计算js文件统一修改为
kotlin
export const data = (core) => {
if (!core) return
result = core
return result
};
所有nunjucks模版提供的函数来控制展示
csharp
{% macro Info() %}
<div class="Info">
</div>
{% endmacro %}
将高清图片的加载放到异步
ini
export const onloadImage = (url) => {
const getImg = (url) =>
new Promise((resolve, reject) => {
const img = new Image();
img.src = url;
img.onload = function () {
resolve({ height: img.height, width: img.width });
};
});
let imginfo = await getImg(url);
}
setTimeout(async function () {
let imageinfo = await onloadImage();
},500)
建立公共文件onload_ExtPack.js控制文件加载
ini
//加载js
export function loadScript(url, type = "text/javascript") {
return new Promise(function (resolve, reject) {
var script = document.createElement("script");
script.type = type;
script.src = url;
document.body.appendChild(script);
script.onload = function () {
resolve(true);
};
});
}
//加载css
export function loadStyle(url) {
let link = document.createElement("link");
link.rel = "stylesheet";
link.type = "text/css";
link.href = url;
document.getElementsByTagName("head")[0].appendChild(link);
}
export async function ExtPack(extpack) {
if (extpack.xxx){
await loadScript(
"./js/xxx?v=" + version,
"text/javascript"
);
loadStyle(
"./css/xxx.min.css"
)
}
}
建立脚本文件,我的做法是由一个主sh脚本文件来读取配置sh脚本文件的内容进行打包
bash
# 配置sh脚本文件的路径
Pack_Path=./pack
# 配置sh脚本文件
pack_project=(
配置1:pack1
配置2:pack2
配置3:pack3
)
echo "开始合并文件..."
echo "文件类型:\n js or html "
read -p "请输入要合并的文件类型 => " type
if [ "$type" == "html" ]; then
echo "已配置脚本文件:\n ${pack_project[*]} "
read -p "配置sh脚本文件 => " code
source $Pack_Path/$code.sh
echo "合并文件目录: $html_PATH "
cat ${html_PATH[*]} > $html_PATH_EXIT
echo -e "合并 $html_PATH_EXIT 完成 "
cat $html_PATH_EXIT $html_PATH_EXIT_cat2 > $cat_PATH
echo -e " 合并 $cat_PATH 完成 "
echo "合并压缩结束..."
fi
if [ "$type" == "js" ]; then
echo "已配置脚本文件:\n ${pack_project[*]} "
read -p "配置sh脚本文件 => " code
source $Pack_Path/$code.sh
cat ${js_PATH_LIST[*]} > $js_PATH_EXIT
echo -e " 合并 $js_PATH_EXIT 完成 "
uglifyjs $js_PATH_EXIT -m -o $js_PATH_EXIT_MIN
echo -e " 压缩 $js_PATH_EXIT_MIN 完成 "
echo "合并压缩结束..."
fi
配置脚本 pack1.sh 目录为pack/pack1.sh
ini
### 需要打包的html ###
html_PATH=(
./html/*.html
)
### 打包后的html存储地址###
html_PATH_EXIT=./program/html/pack1_cat1.html
### 模版1 ###
html_PATH_EXIT_cat2=./program/html/pack1_cat2.html
### html存储地址 ###
cat_PATH=./report/html/pack1.html
# 需要合并的js文件
js_PATH_LIST=(
./js/*.js
)
# js存储地址
js_PATH_EXIT=./build/js/pack1.js
js_PATH_EXIT_MIN=./build/js/pack1.min.js
第二步
基本的步骤已经结束了,运行主文件./pack.sh,就能实现js和html的合并和压缩了
将文件加载替换成打包好的文件
中间总结
目前是基本已经实现了对项目的优化,基本的速度提升是原项目十倍往上!!!👏👏👏客户需求已经达标了。但这就完了吗!不可能,绝对不可能😎
客户的要求达到了,但这个方式最大的缺陷是每次改动都要进行手动运行.sh文件,再输入进行打包之后调试,往往一个小改动或者测试某个函数的时候就会非常麻烦!极其麻烦,而这个项目改动非常频繁。客户舒服了,领导对结果满意了,但对于维护者来说很折磨人。最气人的是😤😤😤,还就折磨我个小小前端😭😭😭。
改!必须改!
怎么改
webpack的打包模式怎么样!!!很不错吧,实时监控文件的改动进行打包,既然webpack能做到我也能做到一点点🤏
查了一下webpack使用nodejs来写的,俺不会咋个办,那就用golang写个服务替代吧!
golang时时监控,调用.sh文件来实现打包服务!
go
package main
import (
"bufio"
"fmt"
"github.com/fsnotify/fsnotify"
"io"
"os"
"os/exec"
"path"
"path/filepath"
"strings"
)
func main() {
watch := NewNotifyFile()
watch.WatchDir("./src")
select {}
}
type NotifyFile struct {
watch *fsnotify.Watcher
}
var REPORT string
func NewNotifyFile() *NotifyFile {
fmt.Scan(&REPORT)
fmt.Println("配置sh文件名:", REPORT)
w := new(NotifyFile)
w.watch, _ = fsnotify.NewWatcher()
return w
}
// 监控目录
func (this *NotifyFile) WatchDir(dir string) {
//通过Walk来遍历目录下的所有子目录
filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
//判断是否为目录,监控目录,目录下文件也在监控范围内,不需要加
if info.IsDir() {
path, err := filepath.Abs(path)
if err != nil {
return err
}
err = this.watch.Add(path)
if err != nil {
return err
}
}
return nil
})
go this.WatchEvent() //协程
}
func (this *NotifyFile) WatchEvent() {
for {
select {
case ev := <-this.watch.Events:
{
if ev.Op&fsnotify.Create == fsnotify.Create {
file, err := os.Stat(ev.Name)
if err == nil && file.IsDir() {
this.watch.Add(ev.Name)
fmt.Println("添加监控 : ", ev.Name)
}
}
if ev.Op&fsnotify.Write == fsnotify.Write {
filesuffix := path.Ext(ev.Name)
fmt.Println("文件后缀名:", filesuffix, filesuffix == ".js")
fmt.Println("修改文件 : ", ev.Name)
if filesuffix == ".js" || filesuffix == ".html" {
write, err := os.OpenFile("pack.sh", os.O_RDWR, 0666)
if err != nil {
fmt.Print(err.Error())
}
defer write.Close()
reader := bufio.NewReader(write)
pos := int64(0)
var pack_type string
if filesuffix == ".js" {
pack_type = "js"
}
if filesuffix == ".html" {
pack_type = "html"
}
for {
//读取每一行内容
line, err := reader.ReadString('\n')
if err != nil {
if err == io.EOF {
fmt.Println("脚本已更新完毕!")
_, err := exec.Command("/bin/bash", "./pack.sh").CombinedOutput()
if err != nil {
fmt.Print(err.Error())
break
} else {
fmt.Print("脚本执行成功!:")
}
break
} else {
fmt.Println("发生了错误:", err)
return
}
}
if strings.Contains(line, "PACK_TYPE=") {
fmt.Println("开始更新打包脚本...")
bytes := []byte("PACK_TYPE=" + pack_type + "\n\n\n\n\n" + "PACK_CODE=" + REPORT + "\n\n\n\n\n\n\n\n\n\n\n\n")
write.WriteAt(bytes, pos)
}
}
}
}
if ev.Op&fsnotify.Remove == fsnotify.Remove {
fmt.Println("删除文件 : ", ev.Name)
fi, err := os.Stat(ev.Name)
if err == nil && fi.IsDir() {
this.watch.Remove(ev.Name)
}
}
}
case err := <-this.watch.Errors:
{
fmt.Println("error : ", err)
return
}
}
}
}
监控加好了,把主.sh文件也改一下🌝
bash
PACK_TYPE=html
PACK_CODE=HM_MA_2201_V
Pack_Path=./report/pack
pack_project=(
配置1:pack1
配置2:pack2
配置3:pack3
)
if [ "$PACK_TYPE" == "html" ]; then
source $Pack_Path/$PACK_CODE.sh
cat ${html_PATH[*]} > $html_PATH_EXIT
cat $html_PATH_EXIT $html_PATH_EXIT_cat2 > $cat_PATH
fi
if [ "$PACK_TYPE" == "js" ]; then
source $Pack_Path/$PACK_CODE.sh
cat ${js_PATH_LIST[*]} > $js_PATH_EXIT
uglifyjs $js_PATH_EXIT -m -o $js_PATH_EXIT_MIN
fi
就这样!实现了
结束🌹🌹🌹
好歹是实现了运行该服务的时候能实现自动打包,不用重复去手动执行.sh文件!但我用golang实现监控和替换文件变量的方式比较粗暴🌚🌚🌚,但在我自己的可控范围之内,还是极大的节省了我的调试时间!
不知道各位大佬有没有更牛逼的实现方式,或者还有什么可以改进的地方!欢迎各位大佬指教。老妹在此感谢大家的阅读,我们一起共同进步!🫶🫶🫶🌹🌹🌹