Optimize libsui js-sdk loading logic
This commit is contained in:
parent
a07c1ece51
commit
5f5af44018
13 changed files with 392 additions and 352 deletions
3
Makefile
3
Makefile
|
|
@ -130,7 +130,8 @@ bindata:
|
|||
cp -r ui .tmp/data/public
|
||||
cp -r xgen .tmp/data/
|
||||
cp -r yao .tmp/data/
|
||||
cp -r builder .tmp/data/
|
||||
cp -r sui/libsui .tmp/data/
|
||||
find .tmp/data -name ".DS_Store" -type f -delete
|
||||
go-bindata -fs -pkg data -o data/bindata.go -prefix ".tmp/data/" .tmp/data/...
|
||||
rm -rf .tmp/data
|
||||
rm -rf .tmp/yao-init
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
function foo() {
|
||||
return "foo";
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
<div>Yao Builder</div>
|
||||
210
data/bindata.go
210
data/bindata.go
File diff suppressed because one or more lines are too long
12
data/data.go
12
data/data.go
|
|
@ -46,18 +46,6 @@ func Setup() *assetfs.AssetFS {
|
|||
panic("unreachable")
|
||||
}
|
||||
|
||||
// Builder Builder ui
|
||||
func Builder() *assetfs.AssetFS {
|
||||
assetInfo := func(path string) (os.FileInfo, error) {
|
||||
return os.Stat(path)
|
||||
}
|
||||
for k := range _bintree.Children {
|
||||
k = "builder"
|
||||
return &assetfs.AssetFS{Asset: Asset, AssetDir: AssetDir, AssetInfo: assetInfo, Prefix: k, Fallback: "index.html"}
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// ReplaceXGen bindata file
|
||||
func ReplaceXGen(search, replace string) error {
|
||||
err := replaceXGenIndex(search, replace)
|
||||
|
|
|
|||
|
|
@ -18,9 +18,6 @@ var AppFileServer http.Handler
|
|||
// XGenFileServerV1 XGen v1.0
|
||||
var XGenFileServerV1 http.Handler = http.FileServer(data.XgenV1())
|
||||
|
||||
// BuilderFileServer Builder ui
|
||||
var BuilderFileServer http.Handler = http.FileServer(data.Builder())
|
||||
|
||||
// AdminRoot cache
|
||||
var AdminRoot = ""
|
||||
|
||||
|
|
|
|||
|
|
@ -71,6 +71,19 @@ func (page *Page) Build(ctx *BuildContext, option *BuildOption) (*goquery.Docume
|
|||
ctx.warnings = append(ctx.warnings, warnings...)
|
||||
}
|
||||
|
||||
// Prepend the libsui.min.js
|
||||
if !option.IgnoreLibSUI {
|
||||
script := ScriptNode{
|
||||
Parent: "head",
|
||||
Attrs: []html.Attribute{
|
||||
{Key: "src", Val: fmt.Sprintf("%s/libsui.min.js", option.AssetRoot)},
|
||||
{Key: "type", Val: "text/javascript"},
|
||||
{Key: "name", Val: "libsui"},
|
||||
},
|
||||
}
|
||||
ctx.scripts = append([]ScriptNode{script}, ctx.scripts...)
|
||||
}
|
||||
|
||||
// Scripts
|
||||
scripts, err := page.BuildScripts(ctx, option, "__page", namespace)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -58,13 +58,6 @@ func (page *Page) Compile(ctx *BuildContext, option *BuildOption) (string, []str
|
|||
|
||||
}
|
||||
|
||||
// SUI lib
|
||||
lib, err := libsui(option.ScriptMinify)
|
||||
if err != nil {
|
||||
return "", warnings, err
|
||||
}
|
||||
head.AppendHtml("\n\n" + `<script name="sui" type="text/javascript">` + lib + `</script>` + "\n\n")
|
||||
|
||||
// Page Config
|
||||
page.Config = page.GetConfig()
|
||||
|
||||
|
|
|
|||
|
|
@ -5,235 +5,39 @@ import (
|
|||
|
||||
"github.com/evanw/esbuild/pkg/api"
|
||||
"github.com/yaoapp/gou/runtime/transform"
|
||||
"github.com/yaoapp/yao/data"
|
||||
)
|
||||
|
||||
var libsuicode = ""
|
||||
|
||||
func libsui(minify bool) (string, error) {
|
||||
if libsuicode != "" {
|
||||
return libsuicode, nil
|
||||
}
|
||||
// LibSUI return the libsui code
|
||||
func LibSUI() ([]byte, []byte, error) {
|
||||
|
||||
option := api.TransformOptions{Target: api.ES2015}
|
||||
if minify {
|
||||
option.MinifyIdentifiers = true
|
||||
option.MinifySyntax = true
|
||||
option.MinifyWhitespace = true
|
||||
}
|
||||
var err error
|
||||
libsuicode, err = transform.JavaScript(libsuisource, option)
|
||||
// Read source code from bindata
|
||||
index, err := data.Read("libsui/index.ts")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("libsui error: %w", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
return libsuicode, nil
|
||||
|
||||
utils, err := data.Read("libsui/utils.ts")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Merge the source code
|
||||
source := fmt.Sprintf("%s\n%s", index, utils)
|
||||
|
||||
// Build the source code
|
||||
js, sm, err := transform.TypeScriptWithSourceMap(string(source), api.TransformOptions{
|
||||
Target: api.ES2015,
|
||||
MinifyIdentifiers: true,
|
||||
MinifySyntax: true,
|
||||
MinifyWhitespace: true,
|
||||
})
|
||||
|
||||
return js, sm, nil
|
||||
}
|
||||
|
||||
const libsuisource = `
|
||||
|
||||
function $$(selector) {
|
||||
elm = null;
|
||||
if (typeof selector === "string" ){
|
||||
elm = document.querySelector(selector);
|
||||
}
|
||||
|
||||
if (selector instanceof HTMLElement) {
|
||||
elm = selector;
|
||||
}
|
||||
|
||||
if (elm) {
|
||||
cn = elm.getAttribute("s:cn");
|
||||
if (cn != "" && typeof window[cn] === "function") {
|
||||
const component = new window[cn](elm);
|
||||
return new __sui_component(elm, component);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const $utils = {
|
||||
|
||||
Store: (elm) => {
|
||||
if (typeof elm === "string") {
|
||||
elm = document.querySelector(elm);
|
||||
}
|
||||
return new __sui_store(elm);
|
||||
},
|
||||
|
||||
RemoveClass: (element, className) => {
|
||||
const classes = Array.isArray(className) ? className : className.split(" ");
|
||||
classes.forEach((c) => {
|
||||
const v = c.replace(/[\n\r\s]/g, "");
|
||||
if (v === "") return;
|
||||
element.classList.remove(v);
|
||||
});
|
||||
return $utils;
|
||||
},
|
||||
|
||||
AddClass: (element, className) => {
|
||||
const classes = Array.isArray(className) ? className : className.split(" ");
|
||||
classes.forEach((c) => {
|
||||
const v = c.replace(/[\n\r\s]/g, "");
|
||||
if (v === "") return;
|
||||
element.classList.add(v);
|
||||
});
|
||||
return $utils;
|
||||
},
|
||||
}
|
||||
|
||||
function __sui_component_root(elm, name) {
|
||||
while (elm && elm.getAttribute("s:cn") !== name) {
|
||||
elm = elm.parentElement;
|
||||
}
|
||||
return elm;
|
||||
}
|
||||
|
||||
function __sui_state(component) {
|
||||
this.handlers = component.watch || {};
|
||||
this.Set = async function (key, value, target) {
|
||||
const handler = this.handlers[key];
|
||||
target = target || component.root;
|
||||
if (handler && typeof handler === "function") {
|
||||
const stateObj = {
|
||||
target: target,
|
||||
stopPropagation: function () {
|
||||
target.setAttribute("state-propagation", "true");
|
||||
},
|
||||
}
|
||||
await handler(value, stateObj);
|
||||
const isStopPropagation = target ? target.getAttribute("state-propagation") === "true" : false;
|
||||
if (isStopPropagation) {
|
||||
return;
|
||||
}
|
||||
|
||||
let parent = component.root.parentElement;
|
||||
while (parent && !parent.getAttribute("s:cn")) {
|
||||
parent = parent.parentElement;
|
||||
}
|
||||
if ( parent == document.body || parent == null) {
|
||||
return;
|
||||
}
|
||||
// Dispatch the state change custom event to parent component
|
||||
const event = new CustomEvent("state:change", { detail: { key: key, value: value, target:component.root } });
|
||||
parent.dispatchEvent(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function __sui_props(elm) {
|
||||
this.Get = function (key) {
|
||||
if (!elm || typeof elm.getAttribute !== "function") {
|
||||
return null;
|
||||
}
|
||||
const k = "prop:" + key;
|
||||
const v = elm.getAttribute(k);
|
||||
const json = elm.getAttribute("json-attr-prop:" + key) === "true";
|
||||
if (json) {
|
||||
try {
|
||||
return JSON.parse(v);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
this.List = function () {
|
||||
const props = {};
|
||||
if (!elm || typeof elm.getAttribute !== "function") {
|
||||
return props;
|
||||
}
|
||||
|
||||
const attrs = elm.attributes;
|
||||
for (let i = 0; i < attrs.length; i++) {
|
||||
const attr = attrs[i];
|
||||
if (attr.name.startsWith("prop:")) {
|
||||
const k = attr.name.replace("prop:", "");
|
||||
const json = elm.getAttribute("json-attr-prop:" + k) === "true";
|
||||
if (json) {
|
||||
try {
|
||||
props[k] = JSON.parse(attr.value);
|
||||
} catch (e) {
|
||||
props[k] = null;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
props[k] = attr.value;
|
||||
}
|
||||
}
|
||||
return props;
|
||||
}
|
||||
}
|
||||
|
||||
function __sui_component(elm, component) {
|
||||
this.root = elm;
|
||||
this.store = new __sui_store(elm);
|
||||
this.props = new __sui_props(elm);
|
||||
this.state = component ? new __sui_state(component) : {};
|
||||
}
|
||||
|
||||
function __sui_event_handler(event, dataKeys, jsonKeys, target, root, handler) {
|
||||
const data = {};
|
||||
target = target || null;
|
||||
if (target) {
|
||||
dataKeys.forEach(function (key) {
|
||||
const value = target.getAttribute("data:" + key);
|
||||
data[key] = value;
|
||||
})
|
||||
jsonKeys.forEach(function (key) {
|
||||
const value = target.getAttribute("json:" + key);
|
||||
data[key] = null;
|
||||
if (value && value != "") {
|
||||
try {
|
||||
data[key] = JSON.parse(value);
|
||||
} catch (e) {
|
||||
const message = e.message || e || "An error occurred";
|
||||
console.error(` + "`[SUI] Event Handler Error: ${message}`" + `, target);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
handler && handler(event, data, {
|
||||
rootElement: root,
|
||||
targetElement: target
|
||||
});
|
||||
};
|
||||
|
||||
function __sui_store(elm) {
|
||||
elm = elm || document.body;
|
||||
|
||||
this.Get = function (key) {
|
||||
return elm.getAttribute("data:" + key);
|
||||
}
|
||||
|
||||
this.Set = function (key, value) {
|
||||
elm.setAttribute("data:" + key, value);
|
||||
}
|
||||
|
||||
this.GetJSON = function (key) {
|
||||
const value = elm.getAttribute("json:" + key);
|
||||
if (value && value != "") {
|
||||
try {
|
||||
const res = JSON.parse(value);
|
||||
return res;
|
||||
} catch (e) {
|
||||
const message = e.message || e || "An error occurred";
|
||||
console.error(` + "`[SUI] Event Handler Error: ${message}`" + `, elm);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
this.SetJSON = function (key, value) {
|
||||
elm.setAttribute("json:" + key, JSON.stringify(value));
|
||||
}
|
||||
|
||||
this.GetData = function () {
|
||||
return this.GetJSON("__component_data") || {};
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const initScriptTmpl = `
|
||||
try {
|
||||
var __sui_data = %s;
|
||||
|
|
|
|||
|
|
@ -262,6 +262,7 @@ type BuildOption struct {
|
|||
PublicRoot string `json:"public_root,omitempty"`
|
||||
AssetRoot string `json:"asset_root,omitempty"`
|
||||
IgnoreAssetRoot bool `json:"ignore_asset_root,omitempty"`
|
||||
IgnoreLibSUI bool `json:"ignore_lib_sui,omitempty"`
|
||||
IgnoreDocument bool `json:"ignore_document,omitempty"`
|
||||
JitMode bool `json:"jit_mode,omitempty"`
|
||||
WithWrapper bool `json:"with_wrapper,omitempty"`
|
||||
|
|
|
|||
177
sui/libsui/index.ts
Normal file
177
sui/libsui/index.ts
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
function $$(selector) {
|
||||
let elm: HTMLElement | null = null;
|
||||
if (typeof selector === "string") {
|
||||
elm = document.querySelector(selector);
|
||||
}
|
||||
|
||||
if (selector instanceof HTMLElement) {
|
||||
elm = selector;
|
||||
}
|
||||
|
||||
if (elm) {
|
||||
const cn = elm.getAttribute("s:cn");
|
||||
if (cn && cn != "" && typeof window[cn] === "function") {
|
||||
const component = new window[cn](elm);
|
||||
return new __sui_component(elm, component);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function __sui_component_root(elm, name) {
|
||||
while (elm && elm.getAttribute("s:cn") !== name) {
|
||||
elm = elm.parentElement;
|
||||
}
|
||||
return elm;
|
||||
}
|
||||
|
||||
function __sui_state(component) {
|
||||
this.handlers = component.watch || {};
|
||||
this.Set = async function (key, value, target) {
|
||||
const handler = this.handlers[key];
|
||||
target = target || component.root;
|
||||
if (handler && typeof handler === "function") {
|
||||
const stateObj = {
|
||||
target: target,
|
||||
stopPropagation: function () {
|
||||
target.setAttribute("state-propagation", "true");
|
||||
},
|
||||
};
|
||||
await handler(value, stateObj);
|
||||
const isStopPropagation = target
|
||||
? target.getAttribute("state-propagation") === "true"
|
||||
: false;
|
||||
if (isStopPropagation) {
|
||||
return;
|
||||
}
|
||||
|
||||
let parent = component.root.parentElement;
|
||||
while (parent && !parent.getAttribute("s:cn")) {
|
||||
parent = parent.parentElement;
|
||||
}
|
||||
if (parent == document.body || parent == null) {
|
||||
return;
|
||||
}
|
||||
// Dispatch the state change custom event to parent component
|
||||
const event = new CustomEvent("state:change", {
|
||||
detail: { key: key, value: value, target: component.root },
|
||||
});
|
||||
parent.dispatchEvent(event);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function __sui_props(elm) {
|
||||
this.Get = function (key) {
|
||||
if (!elm || typeof elm.getAttribute !== "function") {
|
||||
return null;
|
||||
}
|
||||
const k = "prop:" + key;
|
||||
const v = elm.getAttribute(k);
|
||||
const json = elm.getAttribute("json-attr-prop:" + key) === "true";
|
||||
if (json) {
|
||||
try {
|
||||
return JSON.parse(v);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return v;
|
||||
};
|
||||
|
||||
this.List = function () {
|
||||
const props = {};
|
||||
if (!elm || typeof elm.getAttribute !== "function") {
|
||||
return props;
|
||||
}
|
||||
|
||||
const attrs = elm.attributes;
|
||||
for (let i = 0; i < attrs.length; i++) {
|
||||
const attr = attrs[i];
|
||||
if (attr.name.startsWith("prop:")) {
|
||||
const k = attr.name.replace("prop:", "");
|
||||
const json = elm.getAttribute("json-attr-prop:" + k) === "true";
|
||||
if (json) {
|
||||
try {
|
||||
props[k] = JSON.parse(attr.value);
|
||||
} catch (e) {
|
||||
props[k] = null;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
props[k] = attr.value;
|
||||
}
|
||||
}
|
||||
return props;
|
||||
};
|
||||
}
|
||||
|
||||
function __sui_component(elm, component) {
|
||||
this.root = elm;
|
||||
this.store = new __sui_store(elm);
|
||||
this.props = new __sui_props(elm);
|
||||
this.state = component ? new __sui_state(component) : {};
|
||||
}
|
||||
|
||||
function __sui_event_handler(event, dataKeys, jsonKeys, target, root, handler) {
|
||||
const data = {};
|
||||
target = target || null;
|
||||
if (target) {
|
||||
dataKeys.forEach(function (key) {
|
||||
const value = target.getAttribute("data:" + key);
|
||||
data[key] = value;
|
||||
});
|
||||
jsonKeys.forEach(function (key) {
|
||||
const value = target.getAttribute("json:" + key);
|
||||
data[key] = null;
|
||||
if (value && value != "") {
|
||||
try {
|
||||
data[key] = JSON.parse(value);
|
||||
} catch (e) {
|
||||
const message = e.message || e || "An error occurred";
|
||||
console.error(`[SUI] Event Handler Error: ${message} `, target);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
handler &&
|
||||
handler(event, data, {
|
||||
rootElement: root,
|
||||
targetElement: target,
|
||||
});
|
||||
}
|
||||
|
||||
function __sui_store(elm) {
|
||||
elm = elm || document.body;
|
||||
|
||||
this.Get = function (key) {
|
||||
return elm.getAttribute("data:" + key);
|
||||
};
|
||||
|
||||
this.Set = function (key, value) {
|
||||
elm.setAttribute("data:" + key, value);
|
||||
};
|
||||
|
||||
this.GetJSON = function (key) {
|
||||
const value = elm.getAttribute("json:" + key);
|
||||
if (value && value != "") {
|
||||
try {
|
||||
const res = JSON.parse(value);
|
||||
return res;
|
||||
} catch (e) {
|
||||
const message = e.message || e || "An error occurred";
|
||||
console.error(`[SUI] Event Handler Error: ${message}`, elm);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
this.SetJSON = function (key, value) {
|
||||
elm.setAttribute("json:" + key, JSON.stringify(value));
|
||||
};
|
||||
|
||||
this.GetData = function () {
|
||||
return this.GetJSON("__component_data") || {};
|
||||
};
|
||||
}
|
||||
28
sui/libsui/utils.ts
Normal file
28
sui/libsui/utils.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
const $utils = {
|
||||
Store: (elm) => {
|
||||
if (typeof elm === "string") {
|
||||
elm = document.querySelector(elm);
|
||||
}
|
||||
return new __sui_store(elm);
|
||||
},
|
||||
|
||||
RemoveClass: (element, className) => {
|
||||
const classes = Array.isArray(className) ? className : className.split(" ");
|
||||
classes.forEach((c) => {
|
||||
const v = c.replace(/[\n\r\s]/g, "");
|
||||
if (v === "") return;
|
||||
element.classList.remove(v);
|
||||
});
|
||||
return $utils;
|
||||
},
|
||||
|
||||
AddClass: (element, className) => {
|
||||
const classes = Array.isArray(className) ? className : className.split(" ");
|
||||
classes.forEach((c) => {
|
||||
const v = c.replace(/[\n\r\s]/g, "");
|
||||
if (v === "") return;
|
||||
element.classList.add(v);
|
||||
});
|
||||
return $utils;
|
||||
},
|
||||
};
|
||||
|
|
@ -124,6 +124,12 @@ func (tmpl *Template) Build(option *core.BuildOption) ([]string, error) {
|
|||
return warnings, err
|
||||
}
|
||||
|
||||
// Add sui lib to the global
|
||||
err = tmpl.UpdateJSSDK(option)
|
||||
if err != nil {
|
||||
return warnings, err
|
||||
}
|
||||
|
||||
// Execute the build after hook
|
||||
if option.ExecScripts {
|
||||
res := tmpl.ExecAfterBuildScripts()
|
||||
|
|
@ -200,6 +206,42 @@ func (tmpl *Template) SyncAssetFile(file string, option *core.BuildOption) error
|
|||
return copy(sourceFile, targetFile)
|
||||
}
|
||||
|
||||
// UpdateJSSDK update the js sdk
|
||||
func (tmpl *Template) UpdateJSSDK(option *core.BuildOption) error {
|
||||
|
||||
jsCode, sourceMap, err := core.LibSUI()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// get source abs path
|
||||
root, err := tmpl.local.DSL.PublicRoot(option.Data)
|
||||
if err != nil {
|
||||
log.Error("SyncAssets: Get the public root error: %s. use %s", err.Error(), tmpl.local.DSL.Public.Root)
|
||||
root = tmpl.local.DSL.Public.Root
|
||||
}
|
||||
|
||||
targetRoot := filepath.Join(application.App.Root(), "public", root, "assets")
|
||||
|
||||
file := filepath.Join(targetRoot, "libsui.min.js")
|
||||
mapFile := filepath.Join(targetRoot, "libsui.min.js.map")
|
||||
|
||||
// create the target directory
|
||||
if exist, _ := os.Stat(targetRoot); exist == nil {
|
||||
os.MkdirAll(targetRoot, os.ModePerm)
|
||||
}
|
||||
|
||||
// write the js sdk
|
||||
err = os.WriteFile(file, []byte(jsCode), 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// write the source map
|
||||
err = os.WriteFile(mapFile, []byte(sourceMap), 0644)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tmpl *Template) writeGlobalScript(data map[string]interface{}) error {
|
||||
file, source, err := tmpl.backendScriptSource("__global.backend")
|
||||
if err != nil {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue