Golang|Golang 非主流 打包静态资源方案
说到往 Go
程序里面打包进其他非 *.go
资源,在 Go1.16
之前有 go-bindata 第三方开源支持。
在 Go1.16
引入了新特性 embed
,不依赖第三方就能嵌入静态资源权限。
然而在项目中,以上两种方案我都没用,转而自研了一套方案。
不想听我bb心路历程的,直接跳到 开整 环节看最终实现。背景 在2021年初,我在参与的项目 go-mod-graph-chart 时,需要把前端资源集成到一个由Go语言构建的 命令行 程序中。
【Golang|Golang 非主流 打包静态资源方案】都说到这了,就介绍下 go-mod-graph-chart ,它是一个 将
go mod graph
命令输出的文本可视化 命令行 工具(类似于graphviz),在项目目录下执行go mod graph
会输出当前项目,所依赖的第三方包,以及第三方包又依赖了什么包。但当你实际使用时,你会看到一堆的文本,命令输出结果如下:
$ go mod graph
go-learn github.com/antlabs/pcurl@v0.0.7
go-learn github.com/bxcodec/faker/v3@v3.6.0
go-learn github.com/go-sql-driver/mysql@v1.5.0
go-learn github.com/jinzhu/copier@v0.3.5
go-learn github.com/pingcap/parser@v0.0.0-20220118030345-6a854bcbd929
go-learn github.com/smartystreets/goconvey@v1.7.2
go-learn golang.org/x/text@v0.3.7
go-learn moul.io/http2curl@v1.0.0
github.com/antlabs/pcurl@v0.0.7 github.com/gin-contrib/gzip@v0.0.1
github.com/antlabs/pcurl@v0.0.7 github.com/gin-gonic/gin@v1.6.3
github.com/antlabs/pcurl@v0.0.7 github.com/guonaihong/clop@v0.0.9
github.com/antlabs/pcurl@v0.0.7 github.com/guonaihong/gout@v0.0.12
github.com/antlabs/pcurl@v0.0.7 github.com/stretchr/testify@v1.6.1
github.com/pingcap/parser@v0.0.0-20220118030345-6a854bcbd929 github.com/cznic/golex@v0.0.0-20181122101858-9c343928389c
github.com/pingcap/parser@v0.0.0-20220118030345-6a854bcbd929 github.com/cznic/mathutil@v0.0.0-20181122101859-297441e03548
...
而使用 gmchart 这个工具,将其可视化为多级树状结构。
如下图:
在分发这个命令行工具,如果还要带着静态资源,或是让用户先下个
graphviz
,体验就很不好了。 于是我就想有什么办法,将静态资源打包到 *.go
代码里。很不巧在2020年底时,
Go1.16 embed
还未推出 ,但我要解决这个问题。go-bindata
在当时无疑是最受欢迎的方案,但会引入第三方依赖。这个时候,我代码洁癖上来了,之前我用gin做了http服务,后来发现项目只引入了 gin
,我把gin
换成内置的 http
服务 后,就变成无依赖的项目了。所以,我想继续保持 no dependency 。我感觉这个功能应该不难,自己也能写啊。文章图片
实现思路 前端打包,有一步是把所有的
*.js
文件集成到一个 js
文件。并把最终输出的 js
文件名写到 index.html
文件里作为入口js
。Go
静态资源打包,就是把其他类型的文件序列化后,保存到 Go代码
里的静态变量里。Golang
程序在对外提供http服务时,当收到静态资源请求时,就会去读取对应变量,输出到http响应体中,并在 http
heder
中设置对应的 Content-Type
那么如果想办法干预下输出流程,让其写
main.js
, index.html
文件,改为将内容写入到 go
代码的两个变量,就可以实现 Go
打包静态资源了。package gostaticvar IndexHtml = `^M
^Mvar MainJs = `echo "hello";
`var Favicon = `data:image/ico;
base64,AAABAAEAmpsAAAEAIAT...`
开整 项目前端构建用到了 webpack,那就在这上面动动手脚了。
一个 gopher 想要去动 webpack?有点自不量力于是打开了
webpack
的官网,转了一圈,发现官方提供了plugin,通过自定义插件,可以影响其构建流程。这里在
plugin
,获取到了构建结果,通过遍历构建结果,获取到了对于字符串,以及文件名,然后我们又插入了一个新的构建结果 go_static.go
,这里面包含前面的 main.js
, index.html
文件的内容。pack-all-in-go-plugin.js
文件内容如下:class PackAllInGoPlugin {
apply(compiler) {
// emit is asynchronous hook, tapping into it using tapAsync, you can use tapPromise/tap(synchronous) as well
compiler.hooks.emit.tapAsync('PackAllInGoPlugin', (compilation, callback) => {
// Create a header string for the generated file:
var filelist = '';
var indexHtml, mainJs;
var goCode = `package godistfunc GetFile(file string) string {
switch {
case \`index.html\` == file:
return IndexHtml
case \`main.js\` == file:
return MainJs
case \`favicon.ico\` == file:
return Favicon
default:
return ""
}
}var IndexHtml = \`--index.html--\`var MainJs = \`--main.js--\`var Favicon = \`favicon.ico\``// Loop through all compiled assets,
// adding a new line item for each filename.
for (var filename in compilation.assets) {
if ("main.js" == filename) {
let jsCode = compilation.assets[filename].source()
let jsCodeString = jsCode.slice();
jsCodeString = jsCodeString.replace(/\`/g, "\` + \"\`\" + \`")
goCode = goCode.replace('--main.js--', jsCodeString)
} else if ("index.html") {
let htmlCode = compilation.assets[filename].source()
goCode = goCode.replace('--index.html--', htmlCode)
}
}// 将这个列表作为一个新的文件资源,插入到 webpack 构建中:
compilation.assets['../godist/static.go'] = {
source: function() {
return goCode;
},
size: function() {
return goCode.length;
}
};
callback();
});
}
}module.exports = PackAllInGoPlugin;
在
webpack
中引入/*
* 引入自定义插件
*/
const PackAllInGoPlugin = require('./plugin/pack-all-in-go-plugin');
...config = {
pulbins: [
...
new PackAllInGoPlugin({options: true})
],
}
这一通设置后,每次执行
npm run build
就能把最新的静态资源打包进 go_static.go 文件内了。再执行 go build -o main main.go
go
代码和静态资源就打包到一个可执行文件内了。对了!这个 webpack plugin 没发布到 npm ,你如果要用直接把源码抄过去就行了。中间遇到集成问题,可以看看 https://github.com/PaulXu-cn/... ,这个项目实际有在用。最后 总的来说,这次是从前端构建这边来解决了
go
打包静态资源问题,算是横跨 Go
与 WebPack
,这方案算是比较小众,基本属于:gopher
不想碰前端- 前端为什么要给
go
写webpack plugin
好了,我是个爱折腾的
gohper
,这次填了一个我自己制造的坑。如果大家也喜欢捣鼓点不一样的东西,欢迎一起交流。文章图片
参考
- https://xie.infoq.cn/article/...
- https://mp.weixin.qq.com/s/eq...
- https://github.com/go-bindata...
- https://github.com/PaulXu-cn/...
- https://graphviz.org/
- https://webpack.js.org/
推荐阅读
- 不是所有的努力都有回报,并非所有的喜欢都有结果
- 那些只有几行,但是却非常牛逼的代码!
- 《原则》3:激进的公司(非原创)
- 北京涮羊肉哪家强(看沈宏非在高德地图的推荐榜单)
- Golang 给结构体绑定方法
- golang 用channel做锁
- Golang常见类型转换
- 投稿|凉茶非茶,才能救王老吉、和其正们?
- 沃尔克外汇(|沃尔克外汇: 疲弱美元连遭重挫 非农数据或成救命稻草)
- 非此即彼,非分之想