create Playwright struct, use BrowserContext
This commit is contained in:
parent
e5e34362fe
commit
6ef4246565
3 changed files with 92 additions and 60 deletions
|
|
@ -113,15 +113,14 @@ func run(ctx context.Context, ms *xmain.State) (err error) {
|
||||||
}
|
}
|
||||||
ms.Log.Debug.Printf("using layout plugin %s (%s)", envD2Layout, pluginLocation)
|
ms.Log.Debug.Printf("using layout plugin %s (%s)", envD2Layout, pluginLocation)
|
||||||
|
|
||||||
var pw *playwright.Playwright
|
var pw png.Playwright
|
||||||
var browser playwright.Browser
|
|
||||||
if filepath.Ext(outputPath) == ".png" {
|
if filepath.Ext(outputPath) == ".png" {
|
||||||
pw, browser, err = png.InitPlaywright()
|
pw, err = png.InitPlaywright()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer func() error {
|
defer func() error {
|
||||||
err = png.Cleanup(pw, browser)
|
err = pw.Cleanup(*watchFlag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -134,7 +133,7 @@ func run(ctx context.Context, ms *xmain.State) (err error) {
|
||||||
return xmain.UsageErrorf("-w[atch] cannot be combined with reading input from stdin")
|
return xmain.UsageErrorf("-w[atch] cannot be combined with reading input from stdin")
|
||||||
}
|
}
|
||||||
ms.Env.Setenv("LOG_TIMESTAMPS", "1")
|
ms.Env.Setenv("LOG_TIMESTAMPS", "1")
|
||||||
w, err := newWatcher(ctx, ms, plugin, inputPath, outputPath, pw, browser)
|
w, err := newWatcher(ctx, ms, plugin, inputPath, outputPath, pw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -148,7 +147,7 @@ func run(ctx context.Context, ms *xmain.State) (err error) {
|
||||||
_ = 343
|
_ = 343
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = compile(ctx, ms, plugin, inputPath, outputPath, browser)
|
_, err = compile(ctx, ms, plugin, inputPath, outputPath, pw.Page)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -157,7 +156,7 @@ func run(ctx context.Context, ms *xmain.State) (err error) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, inputPath, outputPath string, browser playwright.Browser) ([]byte, error) {
|
func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, inputPath, outputPath string, page playwright.Page) ([]byte, error) {
|
||||||
input, err := ms.ReadPath(inputPath)
|
input, err := ms.ReadPath(inputPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -188,7 +187,7 @@ func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, input
|
||||||
}
|
}
|
||||||
|
|
||||||
if filepath.Ext(outputPath) == ".png" {
|
if filepath.Ext(outputPath) == ".png" {
|
||||||
outputImage, err = png.ExportPNG(browser, svg)
|
outputImage, err = png.ExportPNG(ms, page, svg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,11 +16,11 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fsnotify/fsnotify"
|
"github.com/fsnotify/fsnotify"
|
||||||
"github.com/playwright-community/playwright-go"
|
|
||||||
"nhooyr.io/websocket"
|
"nhooyr.io/websocket"
|
||||||
"nhooyr.io/websocket/wsjson"
|
"nhooyr.io/websocket/wsjson"
|
||||||
|
|
||||||
"oss.terrastruct.com/d2/d2plugin"
|
"oss.terrastruct.com/d2/d2plugin"
|
||||||
|
"oss.terrastruct.com/d2/lib/png"
|
||||||
"oss.terrastruct.com/d2/lib/xbrowser"
|
"oss.terrastruct.com/d2/lib/xbrowser"
|
||||||
"oss.terrastruct.com/d2/lib/xhttp"
|
"oss.terrastruct.com/d2/lib/xhttp"
|
||||||
"oss.terrastruct.com/d2/lib/xmain"
|
"oss.terrastruct.com/d2/lib/xmain"
|
||||||
|
|
@ -63,8 +63,7 @@ type watcher struct {
|
||||||
resMu sync.Mutex
|
resMu sync.Mutex
|
||||||
res *compileResult
|
res *compileResult
|
||||||
|
|
||||||
browser playwright.Browser
|
pw png.Playwright
|
||||||
pw *playwright.Playwright
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type compileResult struct {
|
type compileResult struct {
|
||||||
|
|
@ -72,7 +71,7 @@ type compileResult struct {
|
||||||
SVG string `json:"svg"`
|
SVG string `json:"svg"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func newWatcher(ctx context.Context, ms *xmain.State, layoutPlugin d2plugin.Plugin, inputPath, outputPath string, pw *playwright.Playwright, browser playwright.Browser) (*watcher, error) {
|
func newWatcher(ctx context.Context, ms *xmain.State, layoutPlugin d2plugin.Plugin, inputPath, outputPath string, pw png.Playwright) (*watcher, error) {
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
|
||||||
w := &watcher{
|
w := &watcher{
|
||||||
|
|
@ -87,7 +86,6 @@ func newWatcher(ctx context.Context, ms *xmain.State, layoutPlugin d2plugin.Plug
|
||||||
|
|
||||||
compileCh: make(chan struct{}, 1),
|
compileCh: make(chan struct{}, 1),
|
||||||
wsclients: make(map[*wsclient]struct{}),
|
wsclients: make(map[*wsclient]struct{}),
|
||||||
browser: browser,
|
|
||||||
pw: pw,
|
pw: pw,
|
||||||
}
|
}
|
||||||
err := w.init()
|
err := w.init()
|
||||||
|
|
@ -332,15 +330,16 @@ func (w *watcher) compileLoop(ctx context.Context) error {
|
||||||
recompiledPrefix = "re"
|
recompiledPrefix = "re"
|
||||||
}
|
}
|
||||||
|
|
||||||
if !w.browser.IsConnected() {
|
if filepath.Ext(w.outputPath) == ".png" && !w.pw.Browser.IsConnected() {
|
||||||
newBrowser, err := w.pw.Chromium.Launch()
|
newPW, err := w.pw.RestartBrowser()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
w.ms.Log.Error.Printf("failed to refresh Playwright browser")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
w.browser = newBrowser
|
w.pw = newPW
|
||||||
}
|
}
|
||||||
|
|
||||||
b, err := compile(ctx, w.ms, w.layoutPlugin, w.inputPath, w.outputPath, w.browser)
|
b, err := compile(ctx, w.ms, w.layoutPlugin, w.inputPath, w.outputPath, w.pw.Page)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("failed to %scompile: %w", recompiledPrefix, err)
|
err = fmt.Errorf("failed to %scompile: %w", recompiledPrefix, err)
|
||||||
w.ms.Log.Error.Print(err)
|
w.ms.Log.Error.Print(err)
|
||||||
|
|
|
||||||
120
lib/png/png.go
120
lib/png/png.go
|
|
@ -12,19 +12,69 @@ import (
|
||||||
_ "embed"
|
_ "embed"
|
||||||
|
|
||||||
"github.com/playwright-community/playwright-go"
|
"github.com/playwright-community/playwright-go"
|
||||||
|
"oss.terrastruct.com/d2/lib/xmain"
|
||||||
)
|
)
|
||||||
|
|
||||||
func InitPlaywright() (*playwright.Playwright, playwright.Browser, error) {
|
type Playwright struct {
|
||||||
|
PW *playwright.Playwright
|
||||||
|
Browser playwright.Browser
|
||||||
|
BrowserContext playwright.BrowserContext
|
||||||
|
Page playwright.Page
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pw *Playwright) RestartBrowser() (newPW Playwright, err error) {
|
||||||
|
if err = pw.BrowserContext.Close(); err != nil {
|
||||||
|
return Playwright{}, err
|
||||||
|
}
|
||||||
|
if err = pw.Browser.Close(); err != nil {
|
||||||
|
return Playwright{}, err
|
||||||
|
}
|
||||||
|
browser, err := pw.PW.Chromium.Launch()
|
||||||
|
if err != nil {
|
||||||
|
return Playwright{}, err
|
||||||
|
}
|
||||||
|
context, err := browser.NewContext()
|
||||||
|
if err != nil {
|
||||||
|
return Playwright{}, err
|
||||||
|
}
|
||||||
|
page, err := context.NewPage()
|
||||||
|
if err != nil {
|
||||||
|
return Playwright{}, err
|
||||||
|
}
|
||||||
|
return Playwright{
|
||||||
|
PW: pw.PW,
|
||||||
|
Browser: browser,
|
||||||
|
BrowserContext: context,
|
||||||
|
Page: page,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pw *Playwright) Cleanup(isWatch bool) (err error) {
|
||||||
|
if !isWatch {
|
||||||
|
if err = pw.BrowserContext.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err = pw.Browser.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = pw.PW.Stop(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func InitPlaywright() (Playwright, error) {
|
||||||
// check if playwright driver/browsers are installed and up to date
|
// check if playwright driver/browsers are installed and up to date
|
||||||
// https://github.com/playwright-community/playwright-go/blob/8e8f670b5fa7ba5365ae4bfc123fea4aac359763/run.go#L64.
|
// https://github.com/playwright-community/playwright-go/blob/8e8f670b5fa7ba5365ae4bfc123fea4aac359763/run.go#L64.
|
||||||
driver, err := playwright.NewDriver(&playwright.RunOptions{})
|
driver, err := playwright.NewDriver(&playwright.RunOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return Playwright{}, err
|
||||||
}
|
}
|
||||||
if _, err := os.Stat(driver.DriverBinaryLocation); errors.Is(err, os.ErrNotExist) {
|
if _, err := os.Stat(driver.DriverBinaryLocation); errors.Is(err, os.ErrNotExist) {
|
||||||
err = playwright.Install()
|
err = playwright.Install()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return Playwright{}, err
|
||||||
}
|
}
|
||||||
} else if err == nil {
|
} else if err == nil {
|
||||||
cmd := exec.Command(driver.DriverBinaryLocation, "--version")
|
cmd := exec.Command(driver.DriverBinaryLocation, "--version")
|
||||||
|
|
@ -32,42 +82,44 @@ func InitPlaywright() (*playwright.Playwright, playwright.Browser, error) {
|
||||||
if err != nil || !bytes.Contains(output, []byte(driver.Version)) {
|
if err != nil || !bytes.Contains(output, []byte(driver.Version)) {
|
||||||
err = playwright.Install()
|
err = playwright.Install()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return Playwright{}, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pw, err := playwright.Run()
|
pw, err := playwright.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return Playwright{}, err
|
||||||
}
|
}
|
||||||
browser, err := pw.Chromium.Launch()
|
browser, err := pw.Chromium.Launch()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return Playwright{}, err
|
||||||
}
|
}
|
||||||
return pw, browser, nil
|
context, err := browser.NewContext()
|
||||||
|
if err != nil {
|
||||||
|
return Playwright{}, err
|
||||||
|
}
|
||||||
|
page, err := context.NewPage()
|
||||||
|
if err != nil {
|
||||||
|
return Playwright{}, err
|
||||||
|
}
|
||||||
|
return Playwright{
|
||||||
|
PW: pw,
|
||||||
|
Browser: browser,
|
||||||
|
BrowserContext: context,
|
||||||
|
Page: page,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:embed generate_png.js
|
//go:embed generate_png.js
|
||||||
var genPNGScript string
|
var genPNGScript string
|
||||||
|
|
||||||
func ExportPNG(browser playwright.Browser, svg []byte) (outputImage []byte, err error) {
|
func ExportPNG(ms *xmain.State, page playwright.Page, svg []byte) (outputImage []byte, err error) {
|
||||||
var page playwright.Page
|
if page == nil {
|
||||||
defer func() error {
|
ms.Log.Error.Printf("Playwright was not initialized properly for PNG export")
|
||||||
err = page.Close()
|
return nil, fmt.Errorf("Playwright page is not initialized for png export")
|
||||||
if err != nil {
|
}
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}()
|
|
||||||
|
|
||||||
if browser == nil {
|
|
||||||
return nil, fmt.Errorf("browser is not initialized for png export")
|
|
||||||
}
|
|
||||||
page, err = browser.NewPage()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
encodedSVG := base64.StdEncoding.EncodeToString(svg)
|
encodedSVG := base64.StdEncoding.EncodeToString(svg)
|
||||||
pngInterface, err := page.Evaluate(genPNGScript, "data:image/svg+xml;charset=utf-8;base64,"+encodedSVG)
|
pngInterface, err := page.Evaluate(genPNGScript, "data:image/svg+xml;charset=utf-8;base64,"+encodedSVG)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -77,27 +129,9 @@ func ExportPNG(browser playwright.Browser, svg []byte) (outputImage []byte, err
|
||||||
pngString := fmt.Sprintf("%v", pngInterface)
|
pngString := fmt.Sprintf("%v", pngInterface)
|
||||||
pngPrefix := "data:image/png;base64,"
|
pngPrefix := "data:image/png;base64,"
|
||||||
if !strings.HasPrefix(pngString, pngPrefix) {
|
if !strings.HasPrefix(pngString, pngPrefix) {
|
||||||
|
ms.Log.Error.Printf("failed to convert D2 file to PNG")
|
||||||
return nil, fmt.Errorf("playwright export generated invalid png")
|
return nil, fmt.Errorf("playwright export generated invalid png")
|
||||||
}
|
}
|
||||||
splicedPNGString := pngString[len(pngPrefix):]
|
splicedPNGString := pngString[len(pngPrefix):]
|
||||||
outputImage, err = base64.StdEncoding.DecodeString(splicedPNGString)
|
return base64.StdEncoding.DecodeString(splicedPNGString)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return outputImage, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func Cleanup(pw *playwright.Playwright, browser playwright.Browser) (err error) {
|
|
||||||
if browser != nil {
|
|
||||||
if err = browser.Close(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if pw != nil {
|
|
||||||
if err = pw.Stop(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue