目前,Lynx 并不适合从零开始构建一个新的应用,你需要将 Lynx(引擎)集成自原生移动应用或 Web 应用中,通过 Lynx 视图加载 Lynx 应用。通过几个步骤,你就可以在你的应用中进行 Lynx 开发了。
选择你的目标平台查看具体的集成步骤:
使用 Cocoapods 可以方便的将 Lynx 集成到你的应用中
Lynx Engine 核心能力,包含了解析 Bundle、样式解析、排版以及渲染视图等基础能力。
从 Cocoapods 中获取 Lynx 的最新版本。然后将 Lynx 添加到你的 Podfile 中:
source 'https://cdn.cocoapods.org/'
platform :ios, '10.0'
target 'YourTarget' do
pod 'Lynx', '3.4.1', :subspecs => [
'Framework',
]
pod 'PrimJS', '2.14.1', :subspecs => ['quickjs', 'napi']
end
Lynx Service 包括 LynxImageService
、LynxLogService
等,旨在提供一些宿主应用特性强相关的能力,允许宿主应用在运行时注入自定义实现 Image Service 默认是使用 SDWebImage 图片库实现,在没有集成 SDWebImage 组件的宿主应用上则可以依赖其他图片库。
Lynx 提供了标准的原生 Image、Log、Http 服务的能力,接入方可以快速接入并使用;
从 Cocoapods 中获取 Lynx Service 的最新版本。然后将 Lynx Service 添加到你的 Podfile 中:
source 'https://cdn.cocoapods.org/'
platform :ios, '10.0'
target 'YourTarget' do
pod 'Lynx', '3.4.1', :subspecs => [
'Framework',
]
pod 'PrimJS', '2.14.1', :subspecs => ['quickjs', 'napi']
# integrate image-service, log-service, and http-service
pod 'LynxService', '3.4.1', :subspecs => [
'Image',
'Log',
'Http',
]
# ImageService
pod 'SDWebImage','5.15.5'
pod 'SDWebImageWebPCoder', '0.11.0'
end
XElement 是 Lynx 团队维护的客户端扩展元件集合,提供更丰富的元件能力,能够让 Lynx 能够更快速的被用到生产环境中,提升 Lynx 生态的活力。
从 Cocoapods 中获取 XElement 的最新版本。然后将 XElement 添加到你的 Podfile 中:
source 'https://cdn.cocoapods.org/'
platform :ios, '10.0'
target 'YourTarget' do
pod 'Lynx', '3.4.1', :subspecs => [
'Framework',
]
pod 'PrimJS', '2.14.1', :subspecs => ['quickjs', 'napi']
# integrate image-service, log-service, and http-service
pod 'LynxService', '3.4.1', :subspecs => [
'Image',
'Log',
'Http',
]
# ImageService
pod 'SDWebImage','5.15.5'
pod 'SDWebImageWebPCoder', '0.11.0'
pod 'XElement', '3.4.1'
end
运行 pod install
安装依赖,然后打开你的 XCode 工程,同时需要确保关闭 Sandbox Scripting 能力。
为了关闭 Sandbox Scripting 能力,在 XCode 中点击应用,切换至 Build Settings。过滤 script 关键字,并且将 User Script Sandboxing 置为 NO。
LynxEnv 提供了 Lynx Engine 的全局初始化接口。请保证 LynxEnv 的初始化发生在 Lynx Engine 的任何接口调用之前;例如可以在 AppDelegate 中完成初始化
#import <Lynx/LynxEnv.h>
#import <Lynx/LynxView.h>
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[LynxEnv sharedInstance];
return YES;
}
LynxView 是 Lynx Engine 提供的渲染基本单元,LynxView 是一个继承自 iOS 原生 UIView 的实现,你可以快速的构造一个 LynxView,并将其添加到 ViewController 的视图上。
Lynx Engine 自身并没有资源加载的能力,因此需要在初始化 LynxEnv,或者构造 LynxView 时传入 LynxTemplateProvider
协议的具体实现,Lynx 会采用注入的资源加载器来获取真实的 Bundle 内容
你可以使用多种方式获取 Bundle 的资源内容,在这里我们选择将 Bundle 的内容内置在应用中:
内置文件步骤:
#import <Foundation/Foundation.h>
#import <Lynx/LynxTemplateProvider.h>
NS_ASSUME_NONNULL_BEGIN
@interface DemoLynxProvider : NSObject <LynxTemplateProvider>
@end
NS_ASSUME_NONNULL_END
#import <Foundation/Foundation.h>
#import "DemoLynxProvider.h"
@implementation DemoLynxProvider
- (void)loadTemplateWithUrl:(NSString*)url onComplete:(LynxTemplateLoadBlock)callback {
NSString *filePath = [[NSBundle mainBundle] pathForResource:url ofType:@"bundle"];
if (filePath) {
NSError *error;
NSData *data = [NSData dataWithContentsOfFile:filePath options:0 error:&error];
if (error) {
NSLog(@"Error reading file: %@", error.localizedDescription);
callback(nil, error);
} else {
callback(data, nil);
}
} else {
NSError *urlError = [NSError errorWithDomain:@"com.lynx"
code:400
userInfo:@{NSLocalizedDescriptionKey : @"Invalid URL."}];
callback(nil, urlError);
}
}
@end
按照如下的方式构造一个最基础的 LynxView 实例:
#import <Lynx/LynxView.h>
#import "ViewController.h"
#import "DemoLynxProvider.h"
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
LynxView *lynxView = [[LynxView alloc] initWithBuilderBlock:^(LynxViewBuilder *builder) {
builder.config = [[LynxConfig alloc] initWithProvider:[[DemoLynxProvider alloc] init]];
builder.screenSize = self.view.frame.size;
builder.fontScale = 1.0;
}];
lynxView.preferredLayoutWidth = self.view.frame.size.width;
lynxView.preferredLayoutHeight = self.view.frame.size.height;
lynxView.layoutWidthMode = LynxViewSizeModeExact;
lynxView.layoutHeightMode = LynxViewSizeModeExact;
}
@end
然后将 LynxView 添加到应用视图中:
#import <Lynx/LynxView.h>
#import "ViewController.h"
#import "DemoLynxProvider.h"
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// ...
[self.view addSubview:lynxView];
}
@end
当你完成以上步骤之后,你就已经完成了 LynxView 初始化的全部工作,调用 lynxView.loadTemplateFromURL
方法,即可将对应的 Bundle 渲染到 LynxView 视图上,
#import <Lynx/LynxView.h>
#import "ViewController.h"
#import "DemoLynxProvider.h"
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// ...
[lynxView loadTemplateFromURL:@"main.lynx" initData:nil];
}
@end
然后你将在屏幕上看到如下内容:
恭喜你,现在你现在已经完成了 Lynx Engine 集成的全部工作!
Lynx Engine 核心能力,包含了解析 Bundle、样式解析、排版以及渲染视图等基础能力,以及 Lynx 页面依赖的 JavaScript 运行时基础代码
dependencies {
// lynx dependencies
implementation "org.lynxsdk.lynx:lynx:3.4.1"
implementation "org.lynxsdk.lynx:lynx-jssdk:3.4.1"
implementation "org.lynxsdk.lynx:lynx-trace:3.4.1"
implementation "org.lynxsdk.lynx:primjs:2.14.1"
}
Lynx Service 包括 LynxImageService
、LynxLogService
等,旨在提供一些宿主应用特性强相关的能力,允许宿主应用在运行时注入自定义实现,也可以使用 Lynx 提供的默认实现。
例如 LynxImageService
默认是使用 Fresco 图片库实现,在没有集成 Fresco 组件的应用上则可以依赖其他图片库,比如 Glide 来实现。Lynx 提供了标准的原生 Image、Log、Http 服务的能力,接入方可以快速接入并使用;
dependencies {
// lynx dependencies
implementation "org.lynxsdk.lynx:lynx:3.4.1"
implementation "org.lynxsdk.lynx:lynx-jssdk:3.4.1"
implementation "org.lynxsdk.lynx:lynx-trace:3.4.1"
implementation "org.lynxsdk.lynx:primjs:2.14.1"
// integrating image-service
implementation "org.lynxsdk.lynx:lynx-service-image:3.4.1"
// image-service dependencies, if not added, images cannot be loaded; if the host APP needs to use other image libraries, you can customize the image-service and remove this dependency
implementation "com.facebook.fresco:fresco:2.3.0"
implementation "com.facebook.fresco:animated-gif:2.3.0"
implementation "com.facebook.fresco:animated-webp:2.3.0"
implementation "com.facebook.fresco:webpsupport:2.3.0"
implementation "com.facebook.fresco:animated-base:2.3.0"
implementation "com.squareup.okhttp3:okhttp:4.9.0"
// integrating log-service
implementation "org.lynxsdk.lynx:lynx-service-log:3.4.1"
// integrating http-service
implementation "org.lynxsdk.lynx:lynx-service-http:3.4.1"
}
XElement 是 Lynx 团队维护的客户端扩展元件集合,提供更丰富的元件能力,能够让 Lynx 能够更快速的被用到生产环境中,提升 Lynx 生态的活力。
dependencies {
// lynx dependencies
implementation "org.lynxsdk.lynx:lynx:3.4.1"
implementation "org.lynxsdk.lynx:lynx-jssdk:3.4.1"
implementation "org.lynxsdk.lynx:lynx-trace:3.4.1"
implementation "org.lynxsdk.lynx:primjs:2.14.1"
// integrating image-service
implementation "org.lynxsdk.lynx:lynx-service-image:3.4.1"
// image-service dependencies, if not added, images cannot be loaded; if the host APP needs to use other image libraries, you can customize the image-service and remove this dependency
implementation "com.facebook.fresco:fresco:2.3.0"
implementation "com.facebook.fresco:animated-gif:2.3.0"
implementation "com.facebook.fresco:animated-webp:2.3.0"
implementation "com.facebook.fresco:webpsupport:2.3.0"
implementation "com.facebook.fresco:animated-base:2.3.0"
implementation "com.squareup.okhttp3:okhttp:4.9.0"
// integrating log-service
implementation "org.lynxsdk.lynx:lynx-service-log:3.4.1"
// integrating http-service
implementation "org.lynxsdk.lynx:lynx-service-http:3.4.1"
// integrating XElement
implementation "org.lynxsdk.lynx:xelement:3.4.1"
implementation "org.lynxsdk.lynx:xelement-input:3.4.1"
}
Lynx Engine 混淆规则如下,建议参考最新的源码配置:
# LYNX START
# use @Keep to annotate retained classes.
-dontwarn android.support.annotation.Keep
-keep @android.support.annotation.Keep class **
-keep @android.support.annotation.Keep class ** {
@android.support.annotation.Keep <fields>;
@android.support.annotation.Keep <methods>;
}
-dontwarn androidx.annotation.Keep
-keep @androidx.annotation.Keep class **
-keep @androidx.annotation.Keep class ** {
@androidx.annotation.Keep <fields>;
@androidx.annotation.Keep <methods>;
}
# native method call
-keepclasseswithmembers,includedescriptorclasses class * {
native <methods>;
}
-keepclasseswithmembers class * {
@com.lynx.tasm.base.CalledByNative <methods>;
}
# to customize a module, you need to keep the class name and the method annotated as LynxMethod.
-keepclasseswithmembers class * {
@com.lynx.jsbridge.LynxMethod <methods>;
}
-keepclassmembers class * {
@com.lynx.tasm.behavior.LynxProp <methods>;
@com.lynx.tasm.behavior.LynxPropGroup <methods>;
@com.lynx.tasm.behavior.LynxUIMethod <methods>;
}
-keepclassmembers class com.lynx.tasm.behavior.ui.UIGroup {
public boolean needCustomLayout();
}
# in case R8 compiler may remove mLoader in bytecode.
# as mLoader is not used in java and passed as a WeakRef in JNI.
-keepclassmembers class com.lynx.tasm.LynxTemplateRender {
private com.lynx.tasm.core.LynxResourceLoader mLoader;
}
# the automatically generated setter classes use the class names of LynxBaseUI and ShadowNode and their subclasses.
-keep class com.lynx.tasm.behavior.ui.LynxBaseUI
-keep class com.lynx.tasm.behavior.shadow.ShadowNode
-keep class com.lynx.jsbridge.LynxModule { *; }
-keep class * extends com.lynx.tasm.behavior.ui.LynxBaseUI
-keep class * extends com.lynx.tasm.behavior.shadow.ShadowNode
-keep class * extends com.lynx.jsbridge.LynxModule { *; }
-keep class * extends com.lynx.jsbridge.LynxContextModule
-keep class * implements com.lynx.tasm.behavior.utils.Settable
-keep class * implements com.lynx.tasm.behavior.utils.LynxUISetter
-keep class * implements com.lynx.tasm.behavior.utils.LynxUIMethodInvoker
-keep class com.lynx.tasm.rendernode.compat.**{
*;
}
-keep class com.lynx.tasm.rendernode.compat.RenderNodeFactory{
*;
}
# LYNX END
Application#onCreate
生命周期中完成 Lynx Service 的初始化;Lynx 需要在应用启动时进行一些全局的初始化操作,请在 AndroidManifest.xml 文件中指定你自定义的 Application 类。
<application
android:name=".YourApplication">
</application>
import android.app.Application;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.imagepipeline.core.ImagePipelineConfig;
import com.facebook.imagepipeline.memory.PoolConfig;
import com.facebook.imagepipeline.memory.PoolFactory;
import com.lynx.service.http.LynxHttpService;
import com.lynx.service.image.LynxImageService;
import com.lynx.service.log.LynxLogService;
import com.lynx.tasm.service.LynxServiceCenter;
public class YourApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
initLynxService();
}
private void initLynxService() {
// init Fresco which is needed by LynxImageService
final PoolFactory factory = new PoolFactory(PoolConfig.newBuilder().build());
ImagePipelineConfig.Builder builder =
ImagePipelineConfig.newBuilder(getApplicationContext()).setPoolFactory(factory);
Fresco.initialize(getApplicationContext(), builder.build());
LynxServiceCenter.inst().registerService(LynxImageService.getInstance());
LynxServiceCenter.inst().registerService(LynxLogService.INSTANCE);
LynxServiceCenter.inst().registerService(LynxHttpService.INSTANCE);
}
}
LynxEnv 提供了 Lynx Engine 的全局初始化接口, 请保证 LynxEnv 的初始化发生在 Lynx Engine 的任何接口调用之前; 推荐在应用的 Application#onCreate
生命周期中完成 LynxEnv 的初始化;
import com.lynx.tasm.LynxEnv;
public class YourApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
initLynxService();
initLynxEnv();
}
private void initLynxEnv() {
LynxEnv.inst().init(
this,
null,
null,
null
);
}
}
LynxEnv 初始化方法参数说明如下:
Lynx Engine 自身并没有集成下载资源的能力,因此需要宿主应用来提供 AbsTemplateProvider
的具体实现,并在构造 LynxView 时注入,Lynx 会采用注入的资源加载器来获取真实的 Bundle 内容。
你可以使用多种方式获取 Bundle 的资源内容,在这里我们选择将 Bundle 的内容内置在应用中:
app
└── src
└── main
├── java
├── res
└── assets
└── main.lynx.bundle
import android.content.Context;
import com.lynx.tasm.provider.AbsTemplateProvider;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
public class DemoTemplateProvider extends AbsTemplateProvider {
private Context mContext;
DemoTemplateProvider(Context context) {
this.mContext = context.getApplicationContext();
}
@Override
public void loadTemplate(String uri, Callback callback) {
new Thread(new Runnable() {
@Override
public void run() {
try (InputStream inputStream = mContext.getAssets().open(uri);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
byte[] buffer = new byte[1024];
int length;
while ((length = inputStream.read(buffer)) != -1) {
byteArrayOutputStream.write(buffer, 0, length);
}
callback.onSuccess(byteArrayOutputStream.toByteArray());
} catch (IOException e) {
callback.onFailed(e.getMessage());
}
}
}).start();
}
}
LynxView 是 Lynx Engine 提供的渲染基本单元,LynxView 是继承自 Android 原生 View,你可以快速的构造一个 LynxView,并将其任意添加到原生 Android 视图树上。
import android.app.Activity;
import android.os.Bundle;
import com.lynx.tasm.LynxView;
import com.lynx.tasm.LynxViewBuilder;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LynxView lynxView = buildLynxView();
setContentView(lynxView);
}
private LynxView buildLynxView() {
LynxViewBuilder viewBuilder = new LynxViewBuilder();
viewBuilder.setTemplateProvider(new DemoTemplateProvider(this));
return viewBuilder.build(this);
}
}
XElement 需要额外在LynxViewBuilder
中被引入:
import android.app.Activity;
import android.os.Bundle;
import com.lynx.tasm.LynxView;
import com.lynx.tasm.LynxViewBuilder;
import com.lynx.xelement.XElementBehaviors;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LynxView lynxView = buildLynxView();
setContentView(lynxView);
}
private LynxView buildLynxView() {
LynxViewBuilder viewBuilder = new LynxViewBuilder();
viewBuilder.addBehaviors(new XElementBehaviors().create());
viewBuilder.setTemplateProvider(new DemoTemplateProvider(this));
return viewBuilder.build(this);
}
}
当你完成以上步骤之后,已经完成了 LynxView 创建与资源读取的全部工作,调用 lynxView.renderTemplateUrl
方法,即可将对应的 Bundle 内容渲染到 LynxView 视图上。
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LynxView lynxView = buildLynxView();
setContentView(lynxView);
String url = "main.lynx.bundle";
lynxView.renderTemplateUrl(url, "");
}
}
然后你将在屏幕上看到如下内容:
恭喜你,现在你现在已经完成了 Lynx Engine 集成的全部工作!
Lynx Engine 核心能力,包含了解析 Bundle、样式解析、排版以及渲染视图等基础能力,以及 Lynx 页面依赖的 JavaScript 运行时基础代码。
"dependencies": {
"@ohos/imageknife": "3.2.6",
"@lynx/lynx": "3.4.0",
"@lynx/primjs": "2.14.0",
},
Lynx Service 包括 LynxDevtoolService
、LynxLogService
等,旨在提供一些宿主应用特性强相关的能力,允许宿主应用在运行时注入自定义实现,也可以使用 Lynx 提供的默认实现。例如 LynxHttpService 默认是使用鸿蒙内置的 http 模块实现。Lynx 提供了标准的原生 Log、Http 服务的能力,接入方可以快速接入并使用;
"dependencies": {
"@ohos/imageknife": "3.2.6",
"@lynx/lynx": "3.4.1",
"@lynx/lynx_devtool": "3.4.1",
"@lynx/lynx_devtool_service": "3.4.1",
"@lynx/lynx_http_service": "3.4.1",
"@lynx/lynx_log_service": "3.4.1",
"@lynx/primjs": "2.14.1",
},
为了引入 libc++_shared.so
需要进行 Native C++ 相关配置,需要定义 CMakeLists.txt
。
# the minimum version of CMake.
cmake_minimum_required(VERSION 3.5.0)
project(MyApplication)
并且修改 entry/build-profile.json5
中的 buildOptions
{
buildOption: {
externalNativeOptions: {
path: './src/main/cpp/CMakeLists.txt',
arguments: '',
cppFlags: '',
},
},
}
如果需要请求网络资源,请在 module.json5
配置 requestPermissions
来启用网络请求。
{
module: {
requestPermissions: [
{
name: 'ohos.permission.INTERNET',
reason: '$string:network',
usedScene: {
abilities: ['FormAbility'],
when: 'inuse',
},
},
],
},
}
并且在 entry/src/main/resources/base/element/string.json
中配置 network
关键字段。
{
"string": [
{
"name": "network",
"value": "Request network"
}
]
}
UIAbility#onCreate
生命周期中完成 Lynx Service 的初始化;import { LLog, LynxServiceCenter, LynxEnv, LynxServiceType } from '@lynx/lynx';
import { LynxDevToolService } from '@lynx/lynx_devtool_service';
import { LynxLogService } from '@lynx/lynx_log_service';
import { LynxHttpService } from '@lynx/lynx_http_service';
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
// Init LynxDevtoolService
LynxServiceCenter.registerService(
LynxServiceType.DevTool,
LynxDevToolService.instance,
);
// Init LynxHttpService
LynxServiceCenter.registerService(
LynxServiceType.Http,
LynxHttpService.instance,
);
// Init LynxLogService
LynxServiceCenter.registerService(LynxServiceType.Log, LynxLogService.instance);
}
}
LynxEnv
提供了 Lynx Engine
的全局初始化接口。请保证 LynxEnv
的初始化发生在 Lynx Engine
的任何接口调用之前;例如可以在 EntryAbility#onCreate
生命周期中完成 LynxEnv 的初始化。
import { LLog, LynxEnv } from '@lynx/lynx';
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
// Init LynxService
// ...
// Init LynxEnv
LLog.useSysLog(true);
LynxEnv.initialize(this.context);
let options = new Map<string, string>();
options.set('App', 'LynxExplorer');
options.set('AppVersion', '0.0.1');
LynxEnv.setAppInfo(options);
LynxEnv.enableDevtool(true);
}
}
Lynx Engine
自身并没有集成下载资源的能力,因此需要宿主应用来提供 LynxResourceProvider
的具体实现,并在构造 LynxView
时注入,Lynx
会采用注入的资源加载器来获取真实的 Bundle
内容。
你可以使用多种方式获取 Bundle 的资源内容,在这里我们选择将 Bundle 的内容内置在应用中:
首先请将快速上手阶段生成的 Bundle 文件放置在 src/main/resources/rawfile
目录下;或 者你可以将下面的文件下载到本机上并放置在同样目录下;
entry
└── src
└── main
└── resources
└── rawfile
└── main.lynx.bundle
LynxTemplateResourceFetcher
提供了 Bundle 模板资源的加载能力,你需要实现 fetchTemplate
方法来完成 Bundle 模板资源的加载。
import { LLog, LynxResourceRequest, LynxTemplateResourceFetcher, TemplateProviderResult } from '@lynx/lynx';
import { AsyncCallback, BusinessError } from '@ohos.base';
import http from '@ohos.net.http';
import resourceManager from '@ohos.resourceManager';
export class ExampleTemplateResourceFetcher extends LynxTemplateResourceFetcher {
fetchTemplate(request: LynxResourceRequest,
callback: AsyncCallback<TemplateProviderResult, void>) {
if (request.url.startsWith('http')) {
let httpRequest = http.createHttp();
httpRequest.request(
request.url, {
expectDataType: http.HttpDataType.ARRAY_BUFFER,
}, (err: BusinessError, data: http.HttpResponse) => {
callback(err, {
binary: data?.result as ArrayBuffer
});
httpRequest.destroy();
});
} else {
// local file
const context: Context = getContext(this);
const resourceMgr: resourceManager.ResourceManager = context.resourceManager;
resourceMgr.getRawFileContent(request.url, (err: BusinessError, data: Uint8Array) => {
callback(err, {
binary: data?.buffer as ArrayBuffer
})
});
}
}
fetchSSRData(request: LynxResourceRequest, callback: AsyncCallback<ArrayBuffer, void>) {
let httpRequest = http.createHttp();
httpRequest.request(request.url, {
expectDataType: http.HttpDataType.ARRAY_BUFFER
}, (err: BusinessError, data: http.HttpResponse) => {
callback(err, data?.result as ArrayBuffer)
httpRequest.destroy();
})
}
}
LynxMediaResourceFetcher
提供了媒体资源的加载能力。
import { LynxMediaResourceFetcher, LynxResourceRequest, LynxOptionalBool } from '@lynx/lynx';
export class ExampleMediaResourceFetcher extends LynxMediaResourceFetcher {
shouldRedirectUrl(request: LynxResourceRequest): string {
// just return the input url;
return request.url;
}
isLocalResource(url: string): LynxOptionalBool {
return LynxOptionalBool.UNDEFINED;
}
}
LynxGenericResourceFetcher
提供了通用资源的加载能力,你需要实现 fetchResource
方法来完成通用资源的加载。
import { LynxError, LynxSubErrorCode, LynxGenericResourceFetcher, LynxResourceRequest, LynxResourceType, LynxStreamDelegate } from '@lynx/lynx';
import { AsyncCallback, BusinessError } from '@ohos.base';
import http from '@ohos.net.http';
import { ImageKnife, ImageKnifeOption, CacheStrategy } from '@ohos/imageknife';
export class ExampleGenericResourceFetcher extends LynxGenericResourceFetcher {
fetchResource(request: LynxResourceRequest, callback: AsyncCallback<ArrayBuffer, void>): void {
let httpRequest = http.createHttp();
httpRequest.request(request.url, {
expectDataType: http.HttpDataType.ARRAY_BUFFER
}, (err: BusinessError, data: http.HttpResponse) => {
callback(err, data?.result as ArrayBuffer)
httpRequest.destroy();
})
}
fetchResourcePath(request: LynxResourceRequest, callback: AsyncCallback<string, void>): void {
if (request.type === LynxResourceType.LYNX_RESOURCE_TYPE_IMAGE) {
let option = new ImageKnifeOption();
option.loadSrc = request.url;
option.writeCacheStrategy = CacheStrategy.File;
let error: BusinessError | undefined = undefined;
ImageKnife.getInstance().preLoadCache(option).then((data: string) => {
if (data.length > 0) {
callback(error, data);
} else {
error = {
code: LynxSubErrorCode.E_RESOURCE_IMAGE_PIC_SOURCE,
message: 'Image path is invalid',
name: 'Image Error',
}
callback(error, '');
}
}).catch((e: string) => {
error = {
code: LynxSubErrorCode.E_RESOURCE_IMAGE_FROM_NETWORK_OR_OTHERS,
message: e,
name: 'Image Error',
}
callback(error, '');
})
} else {
callback({
code: LynxError.LYNX_ERROR_CODE_RESOURCE,
message: 'unsupported type: ' + request.type,
name: 'Resource Error',
}, '');
}
}
fetchStream(request: LynxResourceRequest, delegate: LynxStreamDelegate): void {
// TODO(Lynx): support fetching stream.
delegate.onStart(100);
let a = new ArrayBuffer(10);
delegate.onData(a, 0, 10);
delegate.onEnd();
}
cancel(request: LynxResourceRequest): void {
// TODO(Lynx)
}
}
当你完成以上步骤之后,已经完成了 LynxView
创建与资源读取的全部工作,即可将对应的 Bundle 内容渲染到 LynxView
视图上。
import {
LynxTemplateResourceFetcher,
LynxMediaResourceFetcher,
LynxGenericResourceFetcher,
LynxView,
} from '@lynx/lynx';
import { ExampleTemplateResourceFetcher } from '../provider/ExampleTemplateResourceFetcher';
import { ExampleMediaResourceFetcher } from '../provider/ExampleMediaResourceFetcher';
import { ExampleGenericResourceFetcher } from '../provider/ExampleGenericResourceFetcher';
@Entry
@Component
struct Index {
templateResourceFetcher: LynxTemplateResourceFetcher = new ExampleTemplateResourceFetcher();
mediaResourceFetcher: LynxMediaResourceFetcher = new ExampleMediaResourceFetcher();
genericResourceFetcher: LynxGenericResourceFetcher = new ExampleGenericResourceFetcher();
private url: string = 'your bundle file';
build() {
Column() {
LynxView({
templateResourceFetcher: this.templateResourceFetcher,
mediaResourceFetcher: this.mediaResourceFetcher,
genericResourceFetcher: this.genericResourceFetcher,
url: this.url,
}).width('100%').height('100%');
}
.size({ width: '100%', height: '100%' })
}
}
然后你将在屏幕上看到如下内容:
恭喜你,现在你现在已经完成了 Lynx Engine 集成的全部工作!
Lynx for Web 在 Web 浏览器中实现了 Lynx 引擎。通过 Lynx for Web,你可以轻松地将 Lynx 应用集成到任何现有的 Web 项目中,无论该项目使用的是 React、Vue、Svelte 还是纯 HTML。
我们需要你已经阅读并按照快速开始创建了一个 Lynx 项目。
cd <lynx-project-name>
lynx.config.ts
增加 web 配置(environments.web
):import { defineConfig } from '@lynx-js/rspeedy';
import { pluginReactLynx } from '@lynx-js/react-rsbuild-plugin';
export default defineConfig({
plugins: [pluginReactLynx()],
environments: {
web: {
output: {
assetPrefix: '/',
},
},
lynx: {},
},
});
执行:
npm run build
你将会看到项目中多出了 dist/main.lynx.bundle
文件,它就是最终的 web 构建产物。
目前你已经有了一份 Lynx for Web 构建产物,接下来我们需要创建一个 web 工程去使用它,这里使用 Rsbuild。
在上述的 Lynx 项目同级,创建一个新的工程,执行:
npm create rsbuild@latest
跟随提示来创建一个 React 项目。
cd <web-project-name>
npm install @lynx-js/web-core @lynx-js/web-elements
src/app.tsx
引入这些依赖:import './App.css';
import '@lynx-js/web-core/index.css';
import '@lynx-js/web-elements/index.css';
import '@lynx-js/web-core';
import '@lynx-js/web-elements/all';
const App = () => {
return (
<lynx-view
style={{ height: '100vh', width: '100vw' }}
url="/main.web.bundle"
></lynx-view>
);
};
export default App;
rsbuild.config.ts
server.publicDir
需要更换为你实际的 Lynx 项目路径。
import { defineConfig } from '@rsbuild/core';
import { pluginReact } from '@rsbuild/plugin-react';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export default defineConfig({
plugins: [pluginReact()],
server: {
publicDir: [
{
name: path.join(
__dirname,
'../',
// 这里请替换为你实际的 Lynx 项目名称
'lynx-project',
'dist',
),
},
],
},
});
执行:
npm run dev
访问 http://localhost:3000
即能看到你的 Lynx 应用。
完成上述集成后,你可以通过 Lynx for Web 提供的 API 实现更灵活的交互控制,以下是核心 API 的详细说明:
名称 | 是否必传 | 说明 |
---|---|---|
url | 是 | Rspeedy 产物的url(其它 chunk 的 url 会编译时注入产物自动启动) |
globalProps | 否 | 卡片初始化时的 GlobalProps |
initData | 否 | 卡片初始化时的 InitData |
overrideLynxTagToHTMLTagMap | 否 | 自定义 Lynx 标签到 HTML 标签的映射关系。不支持 React Component,只支持 HTMLElement(可以是webcomponent或者原生标签) |
自定义的 NativeModule,key 为 module 名称,value 为 module 实现(一个 esm url):
type NativeModulesMap = Record<string, string>;
示例:
const nativeModulesMap = {
CustomModule: URL.createObjectURL(
new Blob(
[
`export default function(NativeModules, NativeModulesCall) {
return {
async getColor(data, callback) {
const color = await NativeModulesCall('getColor', data);
callback(color);
},
}
};`,
],
{ type: 'text/javascript' },
),
),
};
lynxView.nativeModulesMap = nativeModulesMap;
处理 NativeModules(JSB 等)相关调用的入口:
(name: string, data: any, moduleName: string) => Promise<any> | any;
示例:
// 处理 NativeModule.bridge.call('request')
lynxView.onNativeModulesCall = (name, data, moduleName) => {
if (moduleName === 'bridge') {
if (name === 'request') {
// ...
// return data 会被自动处理为 callback data
return {};
}
}
};
允许用户实现自定义模板加载功能(默认是 fetch):
lynxView.customTemplateLoader = (url) => {
return await(
await fetch(url, {
method: 'GET',
}),
).json();
};
报错信息通知:
type LynxError = CustomEvent<{
error: Error;
sourceMap: {
offset: {
// 行偏移量
line: number;
// 列偏移量
col: number;
};
};
release: string;
fileName: 'lepus.js' | 'app-service.js';
}>;
lynxView.addEventListener('error', (err: LynxError) => {
// ...
});
export type Cloneable<T = string | number | null | boolean | undefined> =
| T
| Record<string, T>
| T[];
updateData(
data: Cloneable,
updateDataType: UpdateDataType,
callback?: () => void,
): void
updateGlobalProps(data: Cloneable): void;
sendGlobalEvent(eventName: string, params: Cloneable[]): void;
lynx-view的内部排版会被强制移出外部排版流
我们会给所有 lynx-view 强制应用 CSS Containment。
也就是默认情况下,您需要给 lynx-view 设置一个宽高。宽高可以是 flex-grow
分配的、可以是百分比指定的,但是不可以是“撑开”的。设置宽高是强烈推荐的做法,也是性能的最佳实践。
有些情况下您的确需要由 lynx-view 的内容决定宽或高,您可以设置 height="auto"
或者 width="auto"
来启动自动宽高监听器。在这种情况下,lynx-view 的内部排版依旧与外部排版流独立。
推荐配置为:Chrome > 118, safari>18, Firefox NSR
如果你想支持 chrome < 118,safari < 18 的浏览器,需要做以下处理:
import '@lynx-js/web-elements-compat/LinearContainer';
@lynx-js
依赖。如果你的项目是 Rsbuild,则按照如下配置修改:// rsbuild.config.ts
export default {
source: {
include: [/@lynx-js/],
},
output: {
polyfill: 'usage',
},
};
Uncaught SecurityError: Failed to construct 'Worker': Script at 'xxx' cannot be accessed from origin 'xxx'
.这是因为 Worker 加载远程脚本需要遵守同源策略,而项目的 JS 资源一般会部署在 CDN 上,从而造成了跨域问题。
可以通过引入 remote-web-worker 的形式解决:
// 引入位置需要保证在 @lynx-js/web-core 前
import 'remote-web-worker';
import '@lynx-js/web-core';
import '@lynx-js/web-core/index.css';
import '@lynx-js/web-elements/all';
import '@lynx-js/web-elements/index.css';
document.body.innerHTML = `
<lynx-view
style="height:100vh; width:100vw;"
url="http://localhost:3000/main/index.main.bundle"
>
</lynx-view>`;
我们提供了 RSBuild 插件来做性能优化,你可以在你的 web 工程中引入该插件