├── .gitignore ├── .vscode └── launch.json ├── LICENSE ├── README.md ├── android ├── .gitignore ├── .idea │ ├── .gitignore │ ├── compiler.xml │ ├── deploymentTargetDropDown.xml │ ├── gradle.xml │ ├── misc.xml │ └── vcs.xml ├── app │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── example │ │ │ └── android │ │ │ └── ExampleInstrumentedTest.java │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── android │ │ │ │ ├── EventMessage.java │ │ │ │ ├── Http.java │ │ │ │ ├── MainActivity.java │ │ │ │ ├── MiniActivity.java │ │ │ │ └── ResponseDTO.java │ │ └── res │ │ │ ├── drawable-v24 │ │ │ └── ic_launcher_foreground.xml │ │ │ ├── drawable │ │ │ └── ic_launcher_background.xml │ │ │ ├── layout │ │ │ ├── activity_main.xml │ │ │ └── activity_mini.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.webp │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.webp │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.webp │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.webp │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.webp │ │ │ └── ic_launcher_round.webp │ │ │ ├── values-night │ │ │ └── themes.xml │ │ │ ├── values │ │ │ ├── colors.xml │ │ │ ├── strings.xml │ │ │ └── themes.xml │ │ │ └── xml │ │ │ ├── backup_rules.xml │ │ │ └── data_extraction_rules.xml │ │ └── test │ │ └── java │ │ └── com │ │ └── example │ │ └── android │ │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle ├── assets └── preview.png ├── index.js ├── package.json ├── packages ├── cjs-utils │ ├── package.json │ └── src │ │ ├── babel.ts │ │ ├── error.ts │ │ ├── fs.ts │ │ ├── index.ts │ │ └── resolve.ts ├── dsbridge │ ├── package.json │ └── src │ │ ├── core.ts │ │ ├── index.ts │ │ └── interface.ts ├── global.d.ts ├── pack │ ├── package.json │ └── src │ │ ├── Compilation.ts │ │ ├── Component.ts │ │ ├── compiler │ │ ├── ast.ts │ │ ├── code-gen.ts │ │ ├── helper.ts │ │ ├── index.ts │ │ ├── parse-template.ts │ │ ├── transform.ts │ │ └── transforms │ │ │ ├── on.ts │ │ │ ├── transformElement.ts │ │ │ ├── transformExpression.ts │ │ │ ├── transformInterpolation.ts │ │ │ └── transformText.ts │ │ ├── index.ts │ │ ├── modules │ │ ├── base.ts │ │ ├── index.ts │ │ ├── json.ts │ │ ├── script.ts │ │ ├── style.ts │ │ └── template.ts │ │ ├── node.ts │ │ └── plugins │ │ ├── GenerateHtmlPlugin.ts │ │ ├── GenerateJsCorePlugin.ts │ │ ├── GenerateWebviewEntryPlugin.ts │ │ ├── GenrateWebviewPlugin.ts │ │ ├── esbuildProcessAppFilePlugin.ts │ │ ├── esbuildProcessComponentScriptPlugin.ts │ │ ├── index.ts │ │ ├── jsCoreBuild.ts │ │ └── webviewBuild.ts ├── runtime-jscore │ ├── package-lock.json │ ├── package.json │ └── src │ │ ├── Application.ts │ │ ├── Component.ts │ │ ├── Instance.ts │ │ ├── Page.ts │ │ ├── bridge-interface │ │ ├── android.ts │ │ └── socket.ts │ │ ├── command.ts │ │ ├── expose.ts │ │ ├── index.ts │ │ ├── patch.ts │ │ └── scheduler.ts ├── runtime-webview │ ├── package.json │ └── src │ │ ├── Component.ts │ │ ├── Node.ts │ │ ├── bridge-interface │ │ ├── android.ts │ │ └── socket.ts │ │ ├── container.ts │ │ ├── helper.ts │ │ ├── htmldomapi.ts │ │ ├── index.ts │ │ ├── modules │ │ ├── attrs.ts │ │ ├── class.ts │ │ ├── eventlisteners.ts │ │ └── mergeData.ts │ │ └── patch.ts ├── server │ ├── package.json │ ├── src │ │ ├── app.ts │ │ ├── controller │ │ │ ├── index.ts │ │ │ └── jsCore.ts │ │ ├── index.ts │ │ ├── middleware │ │ │ ├── cors.middleware.ts │ │ │ └── logger.middleware.ts │ │ └── node │ │ │ └── index.ts │ └── tsconfig.json └── shared │ ├── package.json │ └── src │ ├── const.ts │ ├── index.ts │ ├── object.ts │ ├── protocol.ts │ ├── random.ts │ ├── runtimeHelpers.ts │ └── util.ts ├── pnpm-workspace.yaml ├── rollup.config.js ├── scripts ├── build.js ├── dev.js ├── release.js └── utils.js ├── test ├── package-lock.json ├── package.json ├── project │ ├── app.js │ ├── app.json │ ├── components │ │ ├── goods │ │ │ ├── index.js │ │ │ ├── index.json │ │ │ ├── index.less │ │ │ └── index.pxml │ │ ├── index.json │ │ ├── index.less │ │ ├── index.pxml │ │ └── index.ts │ ├── pages │ │ ├── home │ │ │ ├── data.js │ │ │ ├── index.json │ │ │ ├── index.less │ │ │ ├── index.pxml │ │ │ └── index.ts │ │ └── submit │ │ │ ├── index.json │ │ │ ├── index.less │ │ │ ├── index.pxml │ │ │ └── index.ts │ └── project.config.json ├── test.js └── test.jscore.js └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | .pnpm-debug.log 2 | node_modules 3 | dist 4 | pnpm-lock.yaml -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | "skipFiles": [ 12 | "/**" 13 | ], 14 | "program": "${workspaceFolder}/test/test.jscore.js", 15 | "outFiles": [ 16 | "${workspaceFolder}/**/*.js" 17 | ] 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # po 2 | 小程序技术攻克,此项目可以了解小程序是如何实现的。 3 | 4 | 5 | ## 主要特点 6 | 1. view 和 js 编译成独立两套 7 | 2. view 可运行在浏览器,electron/android/ios 的webview等 8 | 3. js 可运行在android的j2v8,electron和nodejs 9 | 4. view 和 js 通过bridge通信,bridge在不同介质(android/ios/electron)下分别实现 10 | 11 | 12 | ### 涉及知识点 13 | 1. 虚拟DOM 14 | 2. 编译相关知识, ast/code-gen等等 15 | 3. bridge 16 | 17 | 18 | ### 项目结构 19 | 1. dsbridge:抽象bridge功能,供webview/js通信 20 | 2. runtime-jscore: po框架的js运行时部分 21 | 3. runtime-webview: webview运行时部分 22 | 4. server: 服务端运行部分,提供webview和jscore的代码下载,供native运行 23 | 5. android:android app 运行示例 24 | 25 | 26 | ### 语法 27 | 28 | 语法上和微信小程序高度相似 29 | 30 | ```html 31 | 32 | {{item.name}} 33 | 34 | 35 | 36 | ``` 37 | 38 | ```js 39 | 40 | Component({ 41 | data:{ 42 | loaded:false, 43 | goods:[ 44 | { 45 | name:"石榴" 46 | } 47 | ] 48 | }, 49 | onCreated(){ 50 | 51 | }, 52 | onDestroyed(){ 53 | 54 | }, 55 | .... 56 | }) 57 | 58 | ``` 59 | 60 | ### demo演示 61 | 1. pnpm install 62 | 2. npm run build 63 | 3. node test/test.js 64 | 4. 直接在浏览器输入http://localhost:3456/?page=pages/home/index查看 65 | 5. 也可以使用android工程运行小程序 打开android目录下的工程,运行(需要有一定的android开发基础) 66 | 67 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | -------------------------------------------------------------------------------- /android/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /android/.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /android/.idea/deploymentTargetDropDown.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /android/.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 18 | 19 | -------------------------------------------------------------------------------- /android/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /android/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /android/app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | } 4 | 5 | android { 6 | namespace 'com.example.android' 7 | compileSdk 33 8 | 9 | defaultConfig { 10 | applicationId "com.example.android" 11 | minSdk 26 12 | targetSdk 33 13 | versionCode 1 14 | versionName "1.0" 15 | 16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 17 | } 18 | 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | compileOptions { 26 | sourceCompatibility JavaVersion.VERSION_1_8 27 | targetCompatibility JavaVersion.VERSION_1_8 28 | } 29 | } 30 | 31 | dependencies { 32 | 33 | implementation 'androidx.appcompat:appcompat:1.4.1' 34 | implementation 'com.google.android.material:material:1.5.0' 35 | implementation 'androidx.constraintlayout:constraintlayout:2.1.3' 36 | implementation 'com.github.wendux:DSBridge-Android:3.0.0' 37 | implementation 'com.eclipsesource.j2v8:j2v8:6.2.1@aar' 38 | implementation 'com.google.code.gson:gson:2.10.1' 39 | implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.11' 40 | implementation 'com.squareup.okio:okio:3.5.0' 41 | implementation 'com.google.code.gson:gson:2.10.1' 42 | implementation("org.greenrobot:eventbus:3.3.1") 43 | testImplementation 'junit:junit:4.13.2' 44 | androidTestImplementation 'androidx.test.ext:junit:1.1.3' 45 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' 46 | } -------------------------------------------------------------------------------- /android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /android/app/src/androidTest/java/com/example/android/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.example.android; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.platform.app.InstrumentationRegistry; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 24 | assertEquals("com.example.android", appContext.getPackageName()); 25 | } 26 | } -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 18 | 22 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/example/android/EventMessage.java: -------------------------------------------------------------------------------- 1 | package com.example.android; 2 | 3 | public class EventMessage { 4 | int type; 5 | String message; 6 | public EventMessage(int type,String message) { 7 | this.type = type; 8 | this.message = message; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/example/android/Http.java: -------------------------------------------------------------------------------- 1 | package com.example.android; 2 | 3 | 4 | import android.util.Log; 5 | 6 | import java.io.IOException; 7 | import java.util.Map; 8 | 9 | import okhttp3.Call; 10 | import okhttp3.Callback; 11 | import okhttp3.Headers; 12 | import okhttp3.RequestBody; 13 | import okhttp3.Response; 14 | import okhttp3.OkHttpClient; 15 | import okhttp3.Request; 16 | 17 | class Http { 18 | OkHttpClient client = new OkHttpClient(); 19 | // public String postSyncRequest(String url) { 20 | // Headers headers = new Headers 21 | // .Builder() 22 | // .add("Content-Type", "application/json") 23 | // .build(); 24 | // 25 | // return postSyncRequest(url,headers); 26 | // } 27 | 28 | public void getRequest(String url, Callback callback) { 29 | 30 | Request request = new Request.Builder() 31 | .url(url) 32 | .get() 33 | .build(); 34 | 35 | Call call = client.newCall(request); 36 | 37 | call.enqueue(callback); 38 | 39 | } 40 | 41 | public void postRequest(String url, RequestBody body, Callback callback) { 42 | 43 | Request request = new Request.Builder() 44 | .url(url) 45 | .post(body) 46 | .build(); 47 | Call call = client.newCall(request); 48 | 49 | call.enqueue(callback); 50 | } 51 | 52 | 53 | } -------------------------------------------------------------------------------- /android/app/src/main/java/com/example/android/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.android; 2 | 3 | import androidx.appcompat.app.AppCompatActivity; 4 | 5 | import android.content.Intent; 6 | import android.os.Bundle; 7 | 8 | import android.view.View; 9 | 10 | public class MainActivity extends AppCompatActivity { 11 | 12 | @Override 13 | protected void onCreate(Bundle savedInstanceState) { 14 | super.onCreate(savedInstanceState); 15 | setContentView(R.layout.activity_main); 16 | } 17 | 18 | public void onClickOpenMiniProgramActivity(View view) { 19 | startActivity(new Intent(this, MiniActivity.class)); 20 | 21 | } 22 | } -------------------------------------------------------------------------------- /android/app/src/main/java/com/example/android/MiniActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.android; 2 | 3 | import androidx.annotation.NonNull; 4 | import androidx.annotation.Nullable; 5 | import androidx.appcompat.app.AppCompatActivity; 6 | 7 | import android.os.Bundle; 8 | import com.eclipsesource.v8.V8; 9 | import com.eclipsesource.v8.V8Array; 10 | import com.eclipsesource.v8.V8Locker; 11 | import com.google.gson.GsonBuilder; 12 | 13 | import okhttp3.Call; 14 | import okhttp3.Callback; 15 | import okhttp3.MediaType; 16 | import okhttp3.RequestBody; 17 | import okhttp3.Response; 18 | import okio.BufferedSink; 19 | import wendu.dsbridge.CompletionHandler; 20 | import wendu.dsbridge.DWebView; 21 | import wendu.dsbridge.OnReturnValue; 22 | 23 | import android.os.Handler; 24 | import android.os.Message; 25 | import android.text.TextUtils; 26 | import android.util.Log; 27 | import android.view.View; 28 | import android.webkit.JavascriptInterface; 29 | import android.widget.Toast; 30 | import wendu.dsbridge.OnReturnValue; 31 | 32 | import org.greenrobot.eventbus.Subscribe; 33 | import org.greenrobot.eventbus.ThreadMode; 34 | import org.json.JSONException; 35 | import org.json.JSONObject; 36 | 37 | import java.io.IOException; 38 | import java.lang.reflect.Array; 39 | import java.util.ArrayList; 40 | 41 | 42 | import org.greenrobot.eventbus.EventBus; 43 | 44 | 45 | public class MiniActivity extends AppCompatActivity { 46 | V8 jsEngine; 47 | 48 | DWebView dWebView; 49 | 50 | Http http = new Http(); 51 | 52 | String BaseUrl = "http://10.0.2.2:3456"; 53 | 54 | 55 | @Override 56 | protected void onCreate(Bundle savedInstanceState) { 57 | super.onCreate(savedInstanceState); 58 | setContentView(R.layout.activity_mini); 59 | EventBus.getDefault().register(this); 60 | 61 | loadJsCore(); 62 | // executeJsCode(JsCode); 63 | } 64 | 65 | 66 | //3.接收TwoActivity事件处理 67 | @Subscribe(threadMode = ThreadMode.MAIN) 68 | public void onMessageEvent(EventMessage message) throws JSONException { 69 | String msg = message.message; 70 | 71 | JSONObject jsonObject = new JSONObject(msg); 72 | 73 | // ResponseDTO dto = objectFromString(msg,ResponseDTO.class); 74 | executeJsCode(jsonObject.opt("code")); 75 | 76 | } 77 | 78 | 79 | public void executeJsCode(Object c) { 80 | String code = c.toString(); 81 | code += "function add2(){return 'bajie'}"; 82 | this.jsEngine = V8.createV8Runtime(); 83 | this.jsEngine.executeVoidScript(code); 84 | 85 | Log.i("executeJsCode",code); 86 | this.loadWebView(); 87 | } 88 | 89 | 90 | public void loadWebView() { 91 | DWebView.setWebContentsDebuggingEnabled(true); 92 | this.dWebView = findViewById(R.id.webview); 93 | dWebView.addJavascriptObject(MiniActivity.this, null); 94 | 95 | dWebView.loadUrl(BaseUrl + "?page=pages/home/index"); 96 | 97 | 98 | } 99 | 100 | public void loadJsCore() { 101 | 102 | this.http.postRequest(BaseUrl + "/jsCore", new RequestBody() { 103 | @Nullable 104 | @Override 105 | public MediaType contentType() { 106 | return null; 107 | } 108 | 109 | @Override 110 | public void writeTo(@NonNull BufferedSink bufferedSink) throws IOException { 111 | 112 | } 113 | }, new Callback() { 114 | private String JsCode; 115 | 116 | @Override 117 | public void onFailure(@NonNull Call call, @NonNull IOException e) { 118 | 119 | } 120 | 121 | @Override 122 | public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException { 123 | 124 | EventBus.getDefault().post(new EventMessage(0, response.body().string())); 125 | } 126 | }); 127 | } 128 | 129 | 130 | 131 | public static T objectFromString(String jsonString, Class clazzOfT) { 132 | if (TextUtils.isEmpty(jsonString)) { 133 | return null; 134 | } 135 | return new GsonBuilder().create().fromJson(jsonString, clazzOfT); 136 | } 137 | 138 | @JavascriptInterface 139 | public void dsBridgeWebViewMessage(Object msg, CompletionHandler handler){ 140 | 141 | 142 | dWebView.post(new Runnable() { 143 | @Override 144 | public void run() { 145 | String ret = excuteJsCoreFunction(msg); 146 | handler.complete(ret); 147 | } 148 | }); 149 | 150 | 151 | } 152 | 153 | 154 | 155 | 156 | public String excuteJsCoreFunction(Object params) { 157 | 158 | Log.i("excuteJsCoreFunction",params.toString()); 159 | V8Array args = new V8Array(this.jsEngine).push(params.toString()); 160 | String ret = this.jsEngine.executeStringFunction("nativeCallJsCoreFuncName",args); 161 | args.close(); 162 | return ret; 163 | } 164 | 165 | 166 | } -------------------------------------------------------------------------------- /android/app/src/main/java/com/example/android/ResponseDTO.java: -------------------------------------------------------------------------------- 1 | package com.example.android; 2 | 3 | public class ResponseDTO { 4 | private String code; 5 | 6 | public void setCode(String code) { 7 | this.code = code; 8 | } 9 | 10 | public String getCode() { 11 | return code; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /android/app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 |