diff --git a/docs/api/hippy-vue/vue3.md b/docs/api/hippy-vue/vue3.md index cc199bb8215..f16edd989bd 100644 --- a/docs/api/hippy-vue/vue3.md +++ b/docs/api/hippy-vue/vue3.md @@ -154,6 +154,106 @@ const router: Router = createRouter({ }); ``` +# 服务端渲染 + +@hippy/vue-next 现已支持服务端渲染,具体代码可以查看[示例项目](https://github.com/Tencent/Hippy/tree/main/examples/hippy-vue-next-ssr-demo)中的 SSR +部分,关于 Vue SSR 的实现及原理,可以参考[官方文档](https://cn.vuejs.org/guide/scaling-up/ssr.html)。 + +## 如何使用SSR + +请参考[示例项目](https://github.com/Tencent/Hippy/tree/main/examples/hippy-vue-next-ssr-demo)说明文档中的 How To Use SSR + +## 实现原理 + +### SSR 架构图 + +hippy-vue-next SSR 架构图 + +### 详细说明 + +@hippy/vue-next SSR 的实现涉及到了编译时,客户端运行时,以及服务端运行时三个运行环境。在 vue-next ssr的基础上,我们开发了 @hippy/vue-next-server-renderer +用于服务端运行时节点的渲染,开发了 @hippy/vue-next-compiler-ssr 用于编译时 vue 模版文件的编译。以及 @hippy/vue-next-style-parser 用于服务端渲染得到的 +Native Node List 的样式插入。下面我们通过一个模版的编译和运行时过程来说明 @hippy/vue-next SSR 做了哪些事情 + +我们有形如`
`的一段模版 + +- 编译时 + + 模版经过 @hippy/vue-next-compiler-ssr 的处理,得到了形如 + + ```javascript + _push(`{"id":${ssrGetUniqueId()},"index":0,"name":"View","tagName":"div","props":{"class":"test-class","id": "test",},"children":[]},`) + ``` + + 的 render function + +- 服务端运行时 + + 在服务端运行时,编译时得到的 render function 执行后得到了对应节点的 json object。注意 render function 中的 + ssrGetUniqueId 方法,是在 @hippy/vue-next-server-renderer 中提供的,在这里 server-renderer 还会对 + 节点的属性值等进行处理,最后得到 Native Node 的 json object + + ```javascript + { "id":1,"index":0,"name":"View","tagName":"div","props":{"class":"test-class","id": "test",},"children":[] } + ``` + + > 对于手写的非 sfc 模版的渲染函数,在 compiler 中无法处理,也是在 server-renderer 中执行的 + +- 客户端运行时 + + 在客户端运行时,通过 @hippy/vue-next-style-parser,给服务端返回的节点插入样式,并直接调用 hippy native 提供的 + native API,将返回的 Native Node 对象作为参数传入,并完成节点的渲染上屏。 完成节点上屏之后,再通过系统提供的 + global.dynamicLoad 异步加载客户端异步版 jsBundle,完成客户端 Hydrate 并执行后续流程。 + +## 初始化差异 + +SSR 版本的 Demo 初始化与异步版的初始化有一些差异部分,这里对其中的差异部分做一个详细的说明 + +- src/main-native.ts 变更 + +1. 使用 createSSRApp 替换之前的 createApp,createApp 仅支持 CSR 渲染,而 createSSRApp 同时支持 CSR 和 SSR +2. 在初始化时候新增了 ssrNodeList 参数,作为 Hydrate 的初始化节点列表。这里我们服务端返回的初始化节点列表保存在了 global.hippySSRNodes 中,并将其作为参数在createSSRApp时传入 +3. 将 app.mount 放到 router.isReady 完成后调用,因为如果不等待路由完成,会与服务端渲染的节点有所不同,导致 Hydrate 时报错 + +```javascript +- import { createApp } from '@hippy/vue-next'; ++ import { createSSRApp } from '@hippy/vue-next'; +- const app: HippyApp = createApp(App, { ++ const app: HippyApp = createSSRApp(App, { + // ssr rendered node list, use for hydration ++ ssrNodeList: global.hippySSRNodes, +}); ++ router.isReady().then(() => { ++ // mount app ++ app.mount('#root'); ++ }); +``` + +- src/main-server.ts 新增 + +main-server.ts 是在服务端运行的业务 jsBundle,因此不需要做代码分割。整体构建为一个 bundle 即可。其核心功能就是在服务端完成首屏渲染逻辑,并将得到的首屏 Hippy 节点进行处理,插入节点属性和 store(如果存在)后返回, +以及返回当前已生成节点的最大 uniqueId 供客户端后续使用。 + +>注意,服务端代码是同步执行的,如果有数据请求走了异步方式,可能会出现还没有拿到数据,请求就已经返回了的情况。对于这个问题,Vue SSR 提供了专用 API 来处理这个问题: +>[onServerPrefetch](https://cn.vuejs.org/api/composition-api-lifecycle.html#onserverprefetch)。 +>在 [Demo](https://github.com/Tencent/Hippy/blob/main/examples/hippy-vue-next-ssr-demo/src/app.vue) 的 app.vue 中也有 onServerPrefetch 的使用示例 + +- server.ts 新增 + +server.ts 是服务端执行的入口文件,其作用是提供 Web Server,接收客户端的 SSR CGI 请求,并将结果作为响应数据返回给客户端,包括了渲染节点列表,store,以及全局的样式列表。 + +- src/main-client.ts 新增 + +main-client.ts 是客户端执行的入口文件,与之前纯客户端渲染不同,SSR的客户端入口文件仅包含了获取首屏节点请求、插入首屏节点样式、以及将节点插入终端完成渲染的相关逻辑。 + +- src/ssr-node-ops.ts 新增 + +ssr-node-ops.ts 封装了不依赖 @hippy/vue-next 运行时的 SSR 节点的插入,更新,删除等操作逻辑 + +- src/webpack-plugin.ts 新增 + +webpack-plugin.ts 封装了 SSR 渲染所需 Hippy App 的初始化逻辑 + # 其他差异说明 目前 `@hippy/vue-next` 与 `@hippy/vue` 功能上基本对齐,不过在 API 方面与 @hippy/vue 有一些区别,以及还有一些问题还没有解决,这里做些说明: @@ -266,7 +366,7 @@ const router: Router = createRouter({ } ``` - 更多信息可以参考 demo 里的 [extend.ts](https://github.com/Tencent/Hippy/blob/master/examples/hippy-vue-next-demo/src/extend.ts). + 更多信息可以参考 demo 里的 [extend.ts](https://github.com/Tencent/Hippy/blob/main/examples/hippy-vue-next-demo/src/extend.ts). - whitespace 处理 @@ -305,6 +405,10 @@ const router: Router = createRouter({ `` 组件的第一个子元素不能设置 `{ position: absolute }` 样式,如果想将 `` 内容铺满全屏,可以给第一个子元素设置 `{ flex: 1 }` 样式或者显式设置 width 和 height 数值。这与 Hippy3.0 的逻辑保持一致。 +- 书写 SSR 友好的代码 + + 因 SSR 的渲染方式和生命周期等与客户端渲染方式有一些差异,因此需要在代码编写过程中注意,这里可以参考[Vue官方的SSR指引](https://cn.vuejs.org/guide/scaling-up/ssr.html#writing-ssr-friendly-code) + # 示例 -更多使用请参考 [示例项目](https://github.com/Tencent/Hippy/tree/master/examples/hippy-vue-next-demo). +更多使用请参考 [示例项目](https://github.com/Tencent/Hippy/tree/main/examples/hippy-vue-next-demo). diff --git a/docs/assets/img/hippy-vue-next-ssr-arch-cn.png b/docs/assets/img/hippy-vue-next-ssr-arch-cn.png new file mode 100644 index 00000000000..df95922560e Binary files /dev/null and b/docs/assets/img/hippy-vue-next-ssr-arch-cn.png differ diff --git a/driver/js/.eslintrc.js b/driver/js/.eslintrc.js index 99c8ddf95ee..d4ef3af80b6 100644 --- a/driver/js/.eslintrc.js +++ b/driver/js/.eslintrc.js @@ -68,6 +68,7 @@ module.exports = { '@typescript-eslint/consistent-type-assertions': 'off', '@typescript-eslint/naming-convention': 'off', '@typescript-eslint/prefer-for-of': 'off', + '@typescript-eslint/no-require-imports': 'off', }, parserOptions: { project: ['./**/tsconfig.json'], @@ -171,6 +172,8 @@ module.exports = { ['sfc', resolveVue('sfc')], ['he', path.resolve(__dirname, './packages/hippy-vue/src/util/entity-decoder')], ['@hippy-vue-next-style-parser', resolvePackage('hippy-vue-next-style-parser')], + ['@hippy-vue-next', resolvePackage('hippy-vue-next')], + ['@hippy-vue-next-server-renderer', resolvePackage('hippy-vue-next-server-renderer')], ], }, }, diff --git a/driver/js/examples/hippy-vue-next-demo/src/main-native.ts b/driver/js/examples/hippy-vue-next-demo/src/main-native.ts index 69a3e0c7e3a..89e0db5e097 100644 --- a/driver/js/examples/hippy-vue-next-demo/src/main-native.ts +++ b/driver/js/examples/hippy-vue-next-demo/src/main-native.ts @@ -34,6 +34,7 @@ const app: HippyApp = createApp(App, { backgroundColor: 4283416717, // 状态栏背景图,要注意这个会根据容器尺寸拉伸。 + // background image of status bar, scale with wrapper size // backgroundImage: 'https://user-images.githubusercontent.com/12878546/148737148-d0b227cb-69c8-4b21-bf92-739fb0c3f3aa.png', }, }, diff --git a/driver/js/examples/hippy-vue-next-ssr-demo/.gitignore b/driver/js/examples/hippy-vue-next-ssr-demo/.gitignore new file mode 100644 index 00000000000..a0dddc6fb8c --- /dev/null +++ b/driver/js/examples/hippy-vue-next-ssr-demo/.gitignore @@ -0,0 +1,21 @@ +.DS_Store +node_modules +/dist + +# local env files +.env.local +.env.*.local + +# Log files +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Editor directories and files +.idea +.vscode +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/driver/js/examples/hippy-vue-next-ssr-demo/.npmrc b/driver/js/examples/hippy-vue-next-ssr-demo/.npmrc new file mode 100644 index 00000000000..43c97e719a5 --- /dev/null +++ b/driver/js/examples/hippy-vue-next-ssr-demo/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/driver/js/examples/hippy-vue-next-ssr-demo/README.md b/driver/js/examples/hippy-vue-next-ssr-demo/README.md new file mode 100644 index 00000000000..77dccfdfd48 --- /dev/null +++ b/driver/js/examples/hippy-vue-next-ssr-demo/README.md @@ -0,0 +1,46 @@ +# @hippy/vue-next demo + + +### Introduction +This package is the demo project for @hippy/vue-next. Project include most use case for +@hippy/vue-next. Just try it. + +### Usage +Read the hippy framework [doc](https://github.com/Tencent/Hippy/blob/master/README.md#-getting-started) and learn +how to use. + +### How To Use SSR + +we were support SSR for @hippy/vue-next. here is only how to use SSR. how to use vue-next doc is [here](https://hippyjs.org/en-us/#/hippy-vue/vue3) + +1. Before running vue-next-ssr-demo, you should run `npm run init` at root directory to install dependencies and build front-end sdk packages. +2. Then run `cd examples/hippy-vue-next-ssr-demo` and `npm install --legacy-peer-deps` to install demo dependencies. + +Now determine which environment you want build + +> Because our server listening port 8080, so if you are using android device, you should run `adb reverse tcp:8080 tcp:8080` +> to forward mobile device port to pc port, iOS simulator doesn't need this step. + +ensure you were at `examples/hippy-vue-next-ssr-demo`. + +#### Development + +1. run `npm run ssr:dev-build` to build client entry & client bundle, then running hippy debug server +2. run `npm run ssr:dev-server` to build server bundle and start SSR web server to listen port **8080**. +3. debug your app with [reference](https://hippyjs.org/en-us/#/guide/debug) +> You can change server listen port 8080 in `server.ts` by your self, but you also need change request port 8080 in +> `src/main-client.ts` and modify the adb reverse port, ensure port is same at three place + +#### Production + +1. run `npm run ssr:prod-build` to build client entry, server bundle, client bundle +2. run `npm run ssr:prod-server` to start SSR web server to listen port **8080**. +3. test your app +> In production, you can use process manage tool to manage your NodeJs process, like pm2. +> +> And you should deploy you web server at real server with real domain, then you can request +> SSR cgi like https://xxx.com/getSsrFirstScreenData +> + +#### Tips +> Usage of non SSR is [here](https://hippyjs.org/en-us/#/guide/integration) diff --git a/driver/js/examples/hippy-vue-next-ssr-demo/app.d.ts b/driver/js/examples/hippy-vue-next-ssr-demo/app.d.ts new file mode 100644 index 00000000000..d614236bf60 --- /dev/null +++ b/driver/js/examples/hippy-vue-next-ssr-demo/app.d.ts @@ -0,0 +1,10 @@ +declare module '*.jpg'; +declare module '*.png'; +declare module '*.vue' { + import { defineComponent } from 'vue'; + const Component: ReturnType; + export default Component; +} + +type NeedToTyped = any; + diff --git a/driver/js/examples/hippy-vue-next-ssr-demo/package.json b/driver/js/examples/hippy-vue-next-ssr-demo/package.json new file mode 100644 index 00000000000..367d4bffe81 --- /dev/null +++ b/driver/js/examples/hippy-vue-next-ssr-demo/package.json @@ -0,0 +1,83 @@ +{ + "name": "hippy-vue-next-demo", + "version": "3.0.0", + "description": "A SSR Demo Example For Hippy-Vue-Next Library To Show.", + "private": true, + "webMain": "./src/main-web.ts", + "nativeMain": "./src/main-native.ts", + "serverMain": "./src/main-server.ts", + "serverEntry": "./server.ts", + "ssrMain": "./src/main.ts", + "repository": "https://github.com/Tencent/Hippy/tree/master/examples/hippy-vue-next-demo", + "license": "Apache-2.0", + "author": "OpenHippy Team", + "scripts": { + "hippy:dev": "node ./scripts/env-polyfill.js hippy-dev -c ./scripts/hippy-webpack.dev.js", + "web:dev": "npm run hippy:dev & node ./scripts/env-polyfill.js webpack serve --config ./scripts/hippy-webpack.web-renderer.dev.js", + "web:build": "node ./scripts/env-polyfill.js webpack --config ./scripts/hippy-webpack.web-renderer.js", + "ssr:dev-client": "node ./scripts/env-polyfill.js hippy-dev -c ./scripts/webpack-ssr-config/client.dev.js", + "ssr:dev-server": "node ./scripts/env-polyfill.js && node ./scripts/webpack.ssr.dev.js", + "ssr:prod-build": "node ./scripts/webpack.ssr.build.js", + "ssr:prod-server": "node ./dist/server/index.js --mode production" + }, + "dependencies": { + "@hippy/vue-router-next-history": "latest", + "@hippy/web-renderer": "latest", + "@hippy/vue-next": "latest", + "@hippy/vue-next-server-renderer": "file:../../packages/hippy-vue-next-server-renderer", + "@hippy/vue-next-style-parser": "file:../../packages/hippy-vue-next-style-parser", + "@vue/runtime-core": "^3.2.46", + "@vue/shared": "^3.2.46", + "core-js": "^3.20.2", + "vue": "^3.2.46", + "vue-router": "^4.0.12", + "express": "^4.18.2", + "pinia": "2.0.30" + }, + "devDependencies": { + "@babel/core": "^7.12.0", + "@babel/plugin-proposal-class-properties": "^7.10.4", + "@babel/plugin-proposal-decorators": "^7.10.5", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.10.4", + "@babel/plugin-proposal-object-rest-spread": "^7.5.5", + "@babel/plugin-proposal-optional-chaining": "^7.10.4", + "@babel/plugin-transform-async-to-generator": "^7.5.0", + "@babel/plugin-transform-runtime": "^7.11.0", + "@babel/polyfill": "^7.12.0", + "@babel/preset-env": "^7.12.0", + "@babel/runtime": "^7.16.0", + "@hippy/debug-server-next": "latest", + "@hippy/hippy-dynamic-import-plugin": "^2.0.0", + "@hippy/hippy-hmr-plugin": "^0.1.0", + "@hippy/rejection-tracking-polyfill": "^1.0.0", + "@hippy/vue-css-loader": "^2.0.1", + "@vitejs/plugin-vue": "^1.9.4", + "@hippy/vue-next-compiler-ssr": "file:../../packages/hippy-vue-next-compiler-ssr", + "@types/shelljs": "^0.8.5", + "@vue/cli-service": "^4.5.19", + "@vue/compiler-sfc": "^3.2.46", + "babel-loader": "^8.1.0", + "case-sensitive-paths-webpack-plugin": "^2.2.0", + "chokidar": "^3.5.3", + "clean-webpack-plugin": "^4.0.0", + "webpack-manifest-plugin": "^4.1.1", + "cross-env": "^7.0.3", + "cross-env-os": "^7.1.1", + "esbuild": "^0.13.14", + "esbuild-loader": "^2.18.0", + "file-loader": "^4.3.0", + "less": "^4.1.2", + "less-loader": "^7.1.0", + "shelljs": "^0.8.5", + "terser": "^4.8.0", + "ts-loader": "^8.4.0", + "@types/express": "^4.17.17", + "url-loader": "^4.0.0", + "vue-loader": "^17.0.0", + "webpack": "^4.46.0", + "webpack-cli": "^4.7.2" + }, + "engines": { + "node": ">=15" + } +} diff --git a/driver/js/examples/hippy-vue-next-ssr-demo/scripts/env-polyfill.js b/driver/js/examples/hippy-vue-next-ssr-demo/scripts/env-polyfill.js new file mode 100644 index 00000000000..11a413eef85 --- /dev/null +++ b/driver/js/examples/hippy-vue-next-ssr-demo/scripts/env-polyfill.js @@ -0,0 +1,61 @@ +const { exec } = require('shelljs'); + +const runScript = (scriptStr) => { + console.log(`Full command execute: "${scriptStr}"`); + const result = exec(scriptStr, { stdio: 'inherit' }); + if (result.code !== 0) { + console.error(`❌ Execute cmd - "${scriptStr}" error: ${result.stderr}`); + process.exit(1); + } +}; + +const toNum = (originalNum) => { + const num = `${originalNum}`; + const versionList = num.split('.'); + const currentSplitLength = versionList.length; + if (currentSplitLength !== 4) { + let index = currentSplitLength; + while (index < 4) { + versionList.push('0'); + index += 1; + } + } + const r = ['0000', '000', '00', '0', '']; + for (let i = 0; i < versionList.length; i += 1) { + let len = versionList[i].length; + if (len > 4) { + len = 4; + versionList[i] = versionList[i].slice(0, 4); + } + versionList[i] = r[len] + versionList[i]; + } + return versionList.join(''); +}; + +const versionCompare = (targetVer, currentVer) => { + if (!targetVer || !currentVer) return 1; + const numA = toNum(currentVer); + const numB = toNum(targetVer); + if (numA === numB) { + return 0; + } + return numA < numB ? -1 : 1; +}; + +const LEGACY_OPENSSL_VERSION = '3.0.0'; +const scriptString = process.argv.slice(2).join(' '); +let envPrefixStr = ''; + +console.log(`Start to execute cmd: "${scriptString}"`); +console.log(`Current openssl version: ${process.versions.openssl}`); + +const result = /^(\d+\.\d+\.\d+).*$/.exec(process.versions.openssl.toString().trim()); +if (result && result[1]) { + const currentVersion = result[1]; + const compareResult = versionCompare(LEGACY_OPENSSL_VERSION, currentVersion); + if (compareResult >= 0) { + envPrefixStr += 'NODE_OPTIONS=--openssl-legacy-provider'; + } +} + +runScript(`${envPrefixStr} ${scriptString}`); // start to execute cmd diff --git a/driver/js/examples/hippy-vue-next-ssr-demo/scripts/hippy-webpack.dev.js b/driver/js/examples/hippy-vue-next-ssr-demo/scripts/hippy-webpack.dev.js new file mode 100644 index 00000000000..1d5a65f07a7 --- /dev/null +++ b/driver/js/examples/hippy-vue-next-ssr-demo/scripts/hippy-webpack.dev.js @@ -0,0 +1,179 @@ +const fs = require('fs'); +const path = require('path'); +const webpack = require('webpack'); +const HippyDynamicImportPlugin = require('@hippy/hippy-dynamic-import-plugin'); +const { CleanWebpackPlugin } = require('clean-webpack-plugin'); +const { VueLoaderPlugin } = require('vue-loader'); + +const pkg = require('../package.json'); +let cssLoader = '@hippy/vue-css-loader'; +const hippyVueCssLoaderPath = path.resolve(__dirname, '../../../packages/hippy-vue-css-loader/dist/css-loader.js'); +if (fs.existsSync(hippyVueCssLoaderPath)) { + console.warn(`* Using the @hippy/vue-css-loader in ${hippyVueCssLoaderPath}`); + cssLoader = hippyVueCssLoaderPath; +} else { + console.warn('* Using the @hippy/vue-css-loader defined in package.json'); +} + + +module.exports = { + mode: 'development', + devtool: 'eval-source-map', + watch: true, + watchOptions: { + aggregateTimeout: 1500, + }, + devServer: { + // remote debug server address + remote: { + protocol: 'http', + host: '127.0.0.1', + port: 38989, + }, + // support inspect vue components, store and router, by default is disabled + vueDevtools: false, + // support debug multiple project with only one debug server, by default is set false. + multiple: false, + // by default hot and liveReload option are true, you could set only liveReload to true + // to use live reload + hot: true, + liveReload: true, + client: { + overlay: false, + }, + devMiddleware: { + writeToDisk: true, + }, + }, + entry: { + index: ['@hippy/rejection-tracking-polyfill', path.resolve(pkg.nativeMain)], + }, + output: { + filename: 'index.bundle', + // chunkFilename: '[name].[chunkhash].js', + strictModuleExceptionHandling: true, + path: path.resolve('./dist/dev/'), + globalObject: '(0, eval)("this")', + }, + plugins: [ + new VueLoaderPlugin(), + new webpack.DefinePlugin({ + 'process.env': { + NODE_ENV: JSON.stringify('development'), + HOST: JSON.stringify(process.env.DEV_HOST || '127.0.0.1'), + PORT: JSON.stringify(process.env.DEV_PORT || 38989), + }, + __VUE_OPTIONS_API__: true, + __VUE_PROD_DEVTOOLS__: false, + __PLATFORM__: null, + }), + new HippyDynamicImportPlugin(), + // LimitChunkCountPlugin can control dynamic import ability + // Using 1 will prevent any additional chunks from being added + // new webpack.optimize.LimitChunkCountPlugin({ + // maxChunks: 1, + // }), + // use SourceMapDevToolPlugin can generate sourcemap file while setting devtool to false + // new webpack.SourceMapDevToolPlugin({ + // test: /\.(js|jsbundle|css|bundle)($|\?)/i, + // filename: '[file].map', + // }), + new CleanWebpackPlugin(), + ], + module: { + rules: [ + { + test: /\.vue$/, + use: [ + { + loader: 'vue-loader', + options: { + compilerOptions: { + // disable vue3 dom patch flag,because hippy do not support innerHTML + hoistStatic: false, + // whitespace handler, default is 'condense', it can be set 'preserve' + whitespace: 'condense', + }, + }, + }, + ], + }, + { + test: /\.(le|c)ss$/, + use: [cssLoader, 'less-loader'], + }, + { + test: /\.t|js$/, + use: [ + { + loader: 'esbuild-loader', + options: { + target: 'es2015', + }, + }, + ], + }, + { + test: /\.(png|jpe?g|gif)$/i, + use: [{ + loader: 'url-loader', + options: { + limit: true, + // limit: 8192, + // fallback: 'file-loader', + // name: '[name].[ext]', + // outputPath: 'assets/', + }, + }], + }, + { + test: /\.(ts)$/, + use: [ + { + loader: 'ts-loader', + options: { + transpileOnly: true, + appendTsSuffixTo: [/\.vue$/], + }, + }, + ], + exclude: /node_modules/, + }, + { + test: /\.mjs$/, + include: /node_modules/, + type: 'javascript/auto', + }, + ], + }, + resolve: { + extensions: ['.js', '.vue', '.json', '.ts'], + alias: (() => { + const aliases = { + src: path.resolve('./src'), + }; + + // If @vue/runtime-core was built exist in packages directory then make an alias + // Remove the section if you don't use it + const hippyVueRuntimeCorePath = path.resolve(__dirname, '../../../packages/hippy-vue-next/node_modules/@vue/runtime-core'); + if (fs.existsSync(path.resolve(hippyVueRuntimeCorePath, 'index.js'))) { + console.warn(`* Using the @vue/runtime-core in ${hippyVueRuntimeCorePath} as vue alias`); + aliases['@vue/runtime-core'] = hippyVueRuntimeCorePath; + } else { + console.warn('* Using the @vue/runtime-core defined in package.json'); + } + + // If @hippy/vue-next was built exist in packages directory then make an alias + // Remove the section if you don't use it + const hippyVueNextPath = path.resolve(__dirname, '../../../packages/hippy-vue-next/dist'); + if (fs.existsSync(path.resolve(hippyVueNextPath, 'index.js'))) { + console.warn(`* Using the @hippy/vue-next in ${hippyVueNextPath} as @hippy/vue-next alias`); + aliases['@hippy/vue-next'] = hippyVueNextPath; + } else { + console.warn('* Using the @hippy/vue-next defined in package.json'); + } + + return aliases; + })(), + }, +}; diff --git a/driver/js/examples/hippy-vue-next-ssr-demo/scripts/hippy-webpack.web-renderer.dev.js b/driver/js/examples/hippy-vue-next-ssr-demo/scripts/hippy-webpack.web-renderer.dev.js new file mode 100644 index 00000000000..7fc0ec81035 --- /dev/null +++ b/driver/js/examples/hippy-vue-next-ssr-demo/scripts/hippy-webpack.web-renderer.dev.js @@ -0,0 +1,165 @@ +const fs = require('fs'); +const path = require('path'); +const webpack = require('webpack'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); +// const HippyDynamicImportPlugin = require('@hippy/hippy-dynamic-import-plugin'); +const { CleanWebpackPlugin } = require('clean-webpack-plugin'); +const { VueLoaderPlugin } = require('vue-loader'); + +const pkg = require('../package.json'); +let cssLoader = '@hippy/vue-css-loader'; +const hippyVueCssLoaderPath = path.resolve(__dirname, '../../../packages/hippy-vue-css-loader/dist/css-loader.js'); +if (fs.existsSync(hippyVueCssLoaderPath)) { + console.warn(`* Using the @hippy/vue-css-loader in ${hippyVueCssLoaderPath}`); + cssLoader = hippyVueCssLoaderPath; +} else { + console.warn('* Using the @hippy/vue-css-loader defined in package.json'); +} + +const platform = 'web'; +module.exports = { + mode: 'development', + bail: true, + devServer: { + port: 3000, + hot: true, + liveReload: true, + }, + devtool: 'source-map', + entry: { + index: ['regenerator-runtime', path.resolve(pkg.webMain)], + }, + output: { + // filename: `[name].${platform}.js`, + filename: 'index.bundle.js', + path: path.resolve(`./dist/${platform}/`), + strictModuleExceptionHandling: true, + globalObject: '(0, eval)("this")', + }, + plugins: [ + new VueLoaderPlugin(), + new HtmlWebpackPlugin({ + inject: true, + scriptLoading: 'blocking', + template: path.resolve('./public/web-renderer.html'), + }), + new webpack.DefinePlugin({ + 'process.env': { + NODE_ENV: JSON.stringify('development'), + HOST: JSON.stringify(process.env.DEV_HOST || '127.0.0.1'), + PORT: JSON.stringify(process.env.DEV_PORT || 38989), + }, + __VUE_OPTIONS_API__: true, + __VUE_PROD_DEVTOOLS__: false, + __PLATFORM__: platform, + }), + // new HippyDynamicImportPlugin(), + // LimitChunkCountPlugin can control dynamic import ability + // Using 1 will prevent any additional chunks from being added + // new webpack.optimize.LimitChunkCountPlugin({ + // maxChunks: 1, + // }), + // use SourceMapDevToolPlugin can generate sourcemap file while setting devtool to false + // new webpack.SourceMapDevToolPlugin({ + // test: /\.(js|jsbundle|css|bundle)($|\?)/i, + // filename: '[file].map', + // }), + new CleanWebpackPlugin(), + ], + module: { + rules: [ + { + test: /\.vue$/, + use: [ + { + loader: 'vue-loader', + options: { + compilerOptions: { + // disable vue3 dom patch flag,because hippy do not support innerHTML + hoistStatic: false, + // whitespace handler, default is 'condense', it can be set 'preserve' + whitespace: 'condense', + }, + }, + }, + ], + }, + { + test: /\.(le|c)ss$/, + use: [cssLoader, 'less-loader'], + }, + { + test: /\.t|js$/, + use: [ + { + loader: 'esbuild-loader', + options: { + target: 'es2015', + }, + }, + ], + }, + { + test: /\.(png|jpe?g|gif)$/i, + use: [{ + loader: 'url-loader', + options: { + limit: true, + // limit: 8192, + // fallback: 'file-loader', + // name: '[name].[ext]', + // outputPath: 'assets/', + }, + }], + }, + { + test: /\.(ts)$/, + use: [ + { + loader: 'ts-loader', + options: { + transpileOnly: true, + appendTsSuffixTo: [/\.vue$/], + }, + }, + ], + exclude: /node_modules/, + }, + { + test: /\.mjs$/, + include: /node_modules/, + type: 'javascript/auto', + }, + ], + }, + resolve: { + extensions: ['.js', '.vue', '.json', '.ts'], + alias: (() => { + const aliases = { + src: path.resolve('./src'), + }; + + // If @vue/runtime-core was built exist in packages directory then make an alias + // Remove the section if you don't use it + const hippyVueRuntimeCorePath = path.resolve(__dirname, '../../../packages/hippy-vue-next/node_modules/@vue/runtime-core'); + if (fs.existsSync(path.resolve(hippyVueRuntimeCorePath, 'index.js'))) { + console.warn(`* Using the @vue/runtime-core in ${hippyVueRuntimeCorePath} as vue alias`); + aliases['@vue/runtime-core'] = hippyVueRuntimeCorePath; + } else { + console.warn('* Using the @vue/runtime-core defined in package.json'); + } + + // If @hippy/vue-next was built exist in packages directory then make an alias + // Remove the section if you don't use it + const hippyVueNextPath = path.resolve(__dirname, '../../../packages/hippy-vue-next/dist'); + if (fs.existsSync(path.resolve(hippyVueNextPath, 'index.js'))) { + console.warn(`* Using the @hippy/vue-next in ${hippyVueNextPath} as @hippy/vue-next alias`); + aliases['@hippy/vue-next'] = hippyVueNextPath; + } else { + console.warn('* Using the @hippy/vue-next defined in package.json'); + } + + return aliases; + })(), + }, +}; diff --git a/driver/js/examples/hippy-vue-next-ssr-demo/scripts/hippy-webpack.web-renderer.js b/driver/js/examples/hippy-vue-next-ssr-demo/scripts/hippy-webpack.web-renderer.js new file mode 100644 index 00000000000..4e2636dfde5 --- /dev/null +++ b/driver/js/examples/hippy-vue-next-ssr-demo/scripts/hippy-webpack.web-renderer.js @@ -0,0 +1,166 @@ +const path = require('path'); +const fs = require('fs'); +const { VueLoaderPlugin } = require('vue-loader'); +const webpack = require('webpack'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); +// const HippyDynamicImportPlugin = require('@hippy/hippy-dynamic-import-plugin'); +const pkg = require('../package.json'); + +let cssLoader = '@hippy/vue-css-loader'; +const hippyVueCssLoaderPath = path.resolve(__dirname, '../../../packages/hippy-vue-css-loader/dist/css-loader.js'); +if (fs.existsSync(hippyVueCssLoaderPath)) { + console.warn(`* Using the @hippy/vue-css-loader in ${hippyVueCssLoaderPath}`); + cssLoader = hippyVueCssLoaderPath; +} else { + console.warn('* Using the @hippy/vue-css-loader defined in package.json'); +} + +const platform = 'web'; +module.exports = { + mode: 'production', + bail: true, + entry: { + index: ['regenerator-runtime', path.resolve(pkg.webMain)], + }, + output: { + filename: '[name].[contenthash:8].js', + path: path.resolve(`./dist/${platform}/`), + }, + plugins: [ + new VueLoaderPlugin(), + new HtmlWebpackPlugin({ + inject: true, + scriptLoading: 'blocking', + template: path.resolve('./public/web-renderer.html'), + }), + new webpack.DefinePlugin({ + 'process.env': { + NODE_ENV: JSON.stringify('production'), + }, + __PLATFORM__: null, + }), + // new HippyDynamicImportPlugin(), + // LimitChunkCountPlugin can control dynamic import ability + // Using 1 will prevent any additional chunks from being added + // new webpack.optimize.LimitChunkCountPlugin({ + // maxChunks: 1, + // }), + // use SourceMapDevToolPlugin can generate sourcemap file + // new webpack.SourceMapDevToolPlugin({ + // test: /\.(js|jsbundle|css|bundle)($|\?)/i, + // filename: '[file].map', + // }), + ], + module: { + rules: [ + { + test: /\.vue$/, + use: [ + { + loader: 'vue-loader', + options: { + compilerOptions: { + // disable vue3 dom patch flag,because hippy do not support innerHTML + hoistStatic: false, + // whitespace handler, default is 'condense', it can be set 'preserve' + whitespace: 'condense', + }, + }, + }, + ], + }, + { + test: /\.(le|c)ss$/, + use: [cssLoader, 'less-loader'], + }, + { + test: /\.t|js$/, + use: [ + { + loader: 'babel-loader', + options: { + sourceType: 'unambiguous', + presets: [ + [ + '@babel/preset-env', + { + targets: { + chrome: 57, + }, + }, + ], + ], + plugins: [ + ['@babel/plugin-proposal-class-properties'], + ['@babel/plugin-proposal-decorators', { legacy: true }], + ['@babel/plugin-transform-runtime', { regenerator: true }], + ], + }, + }, + ], + }, + { + test: /\.(png|jpe?g|gif)$/i, + use: [{ + loader: 'url-loader', + options: { + limit: true, + // TODO local path not supported on defaultSource/backgroundImage + // limit: 8192, + // fallback: 'file-loader', + // name: '[name].[ext]', + // outputPath: 'assets/', + }, + }], + }, + { + test: /\.(ts)$/, + use: [ + { + loader: 'ts-loader', + options: { + transpileOnly: true, + appendTsSuffixTo: [/\.vue$/], + }, + }, + ], + exclude: /node_modules/, + }, + { + test: /\.mjs$/, + include: /node_modules/, + type: 'javascript/auto', + }, + ], + }, + resolve: { + extensions: ['.js', '.vue', '.json', '.ts'], + alias: (() => { + const aliases = { + src: path.resolve('./src'), + }; + + // If @vue/runtime-core was built exist in packages directory then make an alias + // Remove the section if you don't use it + const hippyVueRuntimeCorePath = path.resolve(__dirname, '../../../packages/hippy-vue-next/node_modules/@vue/runtime-core'); + if (fs.existsSync(path.resolve(hippyVueRuntimeCorePath, 'index.js'))) { + console.warn(`* Using the @vue/runtime-core in ${hippyVueRuntimeCorePath} as vue alias`); + aliases['@vue/runtime-core'] = hippyVueRuntimeCorePath; + } else { + console.warn('* Using the @vue/runtime-core defined in package.json'); + } + + // If @hippy/vue-next was built exist in packages directory then make an alias + // Remove the section if you don't use it + const hippyVueNextPath = path.resolve(__dirname, '../../../packages/hippy-vue-next/dist'); + if (fs.existsSync(path.resolve(hippyVueNextPath, 'index.js'))) { + console.warn(`* Using the @hippy/vue-next in ${hippyVueNextPath} as @hippy/vue-next alias`); + aliases['@hippy/vue-next'] = hippyVueNextPath; + } else { + console.warn('* Using the @hippy/vue-next defined in package.json'); + } + + return aliases; + })(), + }, +}; diff --git a/driver/js/examples/hippy-vue-next-ssr-demo/scripts/webpack-ssr-config/client.android.js b/driver/js/examples/hippy-vue-next-ssr-demo/scripts/webpack-ssr-config/client.android.js new file mode 100644 index 00000000000..bfd96dce07b --- /dev/null +++ b/driver/js/examples/hippy-vue-next-ssr-demo/scripts/webpack-ssr-config/client.android.js @@ -0,0 +1,3 @@ +const { getWebpackSsrBaseConfig } = require('./client.base'); + +module.exports = getWebpackSsrBaseConfig('android', 'production'); diff --git a/driver/js/examples/hippy-vue-next-ssr-demo/scripts/webpack-ssr-config/client.android.vendor.js b/driver/js/examples/hippy-vue-next-ssr-demo/scripts/webpack-ssr-config/client.android.vendor.js new file mode 100644 index 00000000000..85e8b860784 --- /dev/null +++ b/driver/js/examples/hippy-vue-next-ssr-demo/scripts/webpack-ssr-config/client.android.vendor.js @@ -0,0 +1,108 @@ +const fs = require('fs'); +const path = require('path'); +const webpack = require('webpack'); +const { VueLoaderPlugin } = require('vue-loader'); +const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin'); + +const platform = 'android'; + +module.exports = { + mode: 'production', + bail: true, + entry: { + vendor: [path.resolve(__dirname, '../vendor.js')], + }, + output: { + filename: `[name].${platform}.js`, + path: path.resolve(`./dist/${platform}/`), + globalObject: '(0, eval)("this")', + library: 'hippyVueBase', + }, + plugins: [ + new webpack.NamedModulesPlugin(), + new webpack.DefinePlugin({ + 'process.env.NODE_ENV': JSON.stringify('production'), + __PLATFORM__: JSON.stringify(platform), + }), + new CaseSensitivePathsPlugin(), + new VueLoaderPlugin(), + new webpack.DllPlugin({ + context: path.resolve(__dirname, '../..'), + path: path.resolve(__dirname, `../../dist/${platform}/[name]-manifest.json`), + name: 'hippyVueBase', + }), + ], + module: { + rules: [ + { + test: /\.vue$/, + use: [ + { + loader: 'vue-loader', + options: { + compilerOptions: { + // disable vue3 dom patch flag,because hippy do not support innerHTML + hoistStatic: false, + // whitespace handler, default is 'condense', it can be set 'preserve' + whitespace: 'condense', + }, + }, + }, + ], + }, + { + test: /\.(js)$/, + use: [ + { + loader: 'babel-loader', + options: { + presets: [ + [ + '@babel/preset-env', + { + targets: { + chrome: 57, + }, + }, + ], + ], + plugins: [ + ['@babel/plugin-proposal-class-properties'], + ], + }, + }, + ], + }, + ], + }, + resolve: { + extensions: ['.js', '.vue', '.json', '.ts'], + alias: (() => { + const aliases = { + src: path.resolve('./src'), + }; + + // If @vue/runtime-core was built exist in packages directory then make an alias + // Remove the section if you don't use it + const hippyVueRuntimeCorePath = path.resolve(__dirname, '../../../../packages/hippy-vue-next/node_modules/@vue/runtime-core'); + if (fs.existsSync(path.resolve(hippyVueRuntimeCorePath, 'index.js'))) { + console.warn(`* Using the @vue/runtime-core in ${hippyVueRuntimeCorePath} as vue alias`); + aliases['@vue/runtime-core'] = hippyVueRuntimeCorePath; + } else { + console.warn('* Using the @vue/runtime-core defined in package.json'); + } + + // If @hippy/vue-next was built exist in packages directory then make an alias + // Remove the section if you don't use it + const hippyVueNextPath = path.resolve(__dirname, '../../../../packages/hippy-vue-next/dist'); + if (fs.existsSync(path.resolve(hippyVueNextPath, 'index.js'))) { + console.warn(`* Using the @hippy/vue-next in ${hippyVueNextPath} as @hippy/vue-next alias`); + aliases['@hippy/vue-next'] = hippyVueNextPath; + } else { + console.warn('* Using the @hippy/vue-next defined in package.json'); + } + + return aliases; + })(), + }, +}; diff --git a/driver/js/examples/hippy-vue-next-ssr-demo/scripts/webpack-ssr-config/client.base.js b/driver/js/examples/hippy-vue-next-ssr-demo/scripts/webpack-ssr-config/client.base.js new file mode 100644 index 00000000000..36b6537e19d --- /dev/null +++ b/driver/js/examples/hippy-vue-next-ssr-demo/scripts/webpack-ssr-config/client.base.js @@ -0,0 +1,181 @@ +const path = require('path'); +const fs = require('fs'); +const HippyDynamicImportPlugin = require('@hippy/hippy-dynamic-import-plugin'); +const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin'); +const { VueLoaderPlugin } = require('vue-loader'); +const { WebpackManifestPlugin } = require('webpack-manifest-plugin'); +const webpack = require('webpack'); + +const pkg = require('../../package.json'); + +let cssLoader = '@hippy/vue-css-loader'; +const hippyVueCssLoaderPath = path.resolve(__dirname, '../../../../packages/hippy-vue-css-loader/dist/css-loader.js'); +if (fs.existsSync(hippyVueCssLoaderPath)) { + console.warn(`* Using the @hippy/vue-css-loader in ${hippyVueCssLoaderPath}`); + cssLoader = hippyVueCssLoaderPath; +} else { + console.warn('* Using the @hippy/vue-css-loader defined in package.json'); +} + +/** + * get webpack ssr base config + * + * @param platform build platform + * @param env build environment + */ +exports.getWebpackSsrBaseConfig = function (platform, env) { + // do not generate vendor at development + const manifest = require(`../../dist/${platform}/vendor-manifest.json`); + return { + mode: env, + bail: true, + devtool: false, + entry: { + home: [path.resolve(pkg.nativeMain)], + }, + output: { + filename: `[name].${platform}.js`, + path: path.resolve(`./dist/${platform}/`), + globalObject: '(0, eval)("this")', + // CDN path can be configured to load children bundles from remote server + // publicPath: 'https://xxx/hippy/hippyVueNextDemo/', + }, + plugins: [ + new webpack.NamedModulesPlugin(), + new webpack.DefinePlugin({ + 'process.env': { + NODE_ENV: JSON.stringify(env), + }, + __VUE_OPTIONS_API__: true, + __VUE_PROD_DEVTOOLS__: false, + __PLATFORM__: JSON.stringify(platform), + }), + new CaseSensitivePathsPlugin(), + new VueLoaderPlugin(), + new HippyDynamicImportPlugin(), + new WebpackManifestPlugin({ + fileName: `manifest.${platform}.json`, + }), + new webpack.DllReferencePlugin({ + context: path.resolve(__dirname, '../..'), + manifest, + }), + ], + module: { + rules: [ + { + test: /\.vue$/, + use: [ + { + loader: 'vue-loader', + options: { + compilerOptions: { + // disable vue3 dom patch flag,because hippy do not support innerHTML + hoistStatic: false, + // whitespace handler, default is 'condense', it can be set 'preserve' + whitespace: 'condense', + // do not generate html comment node + comments: false, + }, + }, + }, + ], + }, + { + test: /\.(le|c)ss$/, + use: [cssLoader, 'less-loader'], + }, + { + test: /\.t|js$/, + use: [ + { + loader: 'babel-loader', + options: { + sourceType: 'unambiguous', + presets: [ + [ + '@babel/preset-env', + { + targets: platform === 'android' ? { + chrome: 57, + } : { + ios: 9, + }, + }, + ], + ], + plugins: [ + ['@babel/plugin-proposal-class-properties'], + ['@babel/plugin-proposal-decorators', { legacy: true }], + ['@babel/plugin-transform-runtime', { regenerator: true }], + ], + }, + }, + ], + }, + { + test: /\.(png|jpe?g|gif)$/i, + use: [{ + loader: 'url-loader', + options: { + // if you would like to use base64 for picture, uncomment limit: true + // limit: true, + limit: 1024, + fallback: 'file-loader', + name: '[name].[ext]', + outputPath: 'assets/', + }, + }], + }, + { + test: /\.(ts)$/, + use: [ + { + loader: 'ts-loader', + options: { + transpileOnly: true, + appendTsSuffixTo: [/\.vue$/], + }, + }, + ], + exclude: /node_modules/, + }, + { + test: /\.mjs$/, + include: /node_modules/, + type: 'javascript/auto', + }, + ], + }, + resolve: { + extensions: ['.js', '.vue', '.json', '.ts'], + alias: (() => { + const aliases = { + src: path.resolve('./src'), + }; + + // If @vue/runtime-core was built exist in packages directory then make an alias + // Remove the section if you don't use it + const hippyVueRuntimeCorePath = path.resolve(__dirname, '../../../../packages/hippy-vue-next/node_modules/@vue/runtime-core'); + if (fs.existsSync(path.resolve(hippyVueRuntimeCorePath, 'index.js'))) { + console.warn(`* Using the @vue/runtime-core in ${hippyVueRuntimeCorePath} as vue alias`); + aliases['@vue/runtime-core'] = hippyVueRuntimeCorePath; + } else { + console.warn('* Using the @vue/runtime-core defined in package.json'); + } + + // If @hippy/vue-next was built exist in packages directory then make an alias + // Remove the section if you don't use it + const hippyVueNextPath = path.resolve(__dirname, '../../../../packages/hippy-vue-next/dist'); + if (fs.existsSync(path.resolve(hippyVueNextPath, 'index.js'))) { + console.warn(`* Using the @hippy/vue-next in ${hippyVueNextPath} as @hippy/vue-next alias`); + aliases['@hippy/vue-next'] = hippyVueNextPath; + } else { + console.warn('* Using the @hippy/vue-next defined in package.json'); + } + + return aliases; + })(), + }, + }; +}; diff --git a/driver/js/examples/hippy-vue-next-ssr-demo/scripts/webpack-ssr-config/client.dev.js b/driver/js/examples/hippy-vue-next-ssr-demo/scripts/webpack-ssr-config/client.dev.js new file mode 100644 index 00000000000..f465c4ff488 --- /dev/null +++ b/driver/js/examples/hippy-vue-next-ssr-demo/scripts/webpack-ssr-config/client.dev.js @@ -0,0 +1,195 @@ +const path = require('path'); +const fs = require('fs'); +const HippyDynamicImportPlugin = require('@hippy/hippy-dynamic-import-plugin'); +const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin'); +const { VueLoaderPlugin } = require('vue-loader'); +const webpack = require('webpack'); + +const pkg = require('../../package.json'); + +let cssLoader = '@hippy/vue-css-loader'; +const hippyVueCssLoaderPath = path.resolve(__dirname, '../../../../packages/hippy-vue-css-loader/dist/css-loader.js'); +if (fs.existsSync(hippyVueCssLoaderPath)) { + console.warn(`* Using the @hippy/vue-css-loader in ${hippyVueCssLoaderPath}`); + cssLoader = hippyVueCssLoaderPath; +} else { + console.warn('* Using the @hippy/vue-css-loader defined in package.json'); +} + +/** + * webpack ssr client dev config + */ +module.exports = { + mode: 'development', + bail: true, + devtool: 'eval-source-map', + watch: true, + watchOptions: { + // file changed, rebuild delay time + aggregateTimeout: 1000, + }, + devServer: { + remote: { + protocol: 'http', + host: '127.0.0.1', + port: 38989, + }, + // support vue dev tools,default is false + vueDevtools: false, + // not support one debug server debug multiple app + multiple: false, + // ssr do not support hot replacement now + hot: false, + // default is true + liveReload: false, + client: { + // hippy do not support error tips layer + overlay: false, + }, + devMiddleware: { + // write hot replacement file to disk + writeToDisk: true, + }, + }, + entry: { + // client async bundle + home: [path.resolve(pkg.nativeMain)], + // client ssr entry + index: [path.resolve(pkg.ssrMain)], + }, + output: { + filename: '[name].bundle', + path: path.resolve('./dist/dev/'), + globalObject: '(0, eval)("this")', + }, + plugins: [ + new webpack.NamedModulesPlugin(), + new webpack.DefinePlugin({ + 'process.env': { + NODE_ENV: JSON.stringify('development'), + HOST: JSON.stringify(process.env.DEV_HOST || '127.0.0.1'), + PORT: JSON.stringify(process.env.DEV_PORT || 38989), + }, + __VUE_OPTIONS_API__: true, + __VUE_PROD_DEVTOOLS__: false, + __PLATFORM__: null, + }), + new CaseSensitivePathsPlugin(), + new VueLoaderPlugin(), + new HippyDynamicImportPlugin(), + ], + module: { + rules: [ + { + test: /\.vue$/, + use: [ + { + loader: 'vue-loader', + options: { + compilerOptions: { + // disable vue3 dom patch flag,because hippy do not support innerHTML + hoistStatic: false, + // whitespace handler, default is 'condense', it can be set 'preserve' + whitespace: 'condense', + // do not generate html comment node + comments: false, + }, + }, + }, + ], + }, + { + test: /\.(le|c)ss$/, + use: [cssLoader, 'less-loader'], + }, + { + test: /\.t|js$/, + use: [ + { + loader: 'babel-loader', + options: { + sourceType: 'unambiguous', + presets: [ + [ + '@babel/preset-env', + { + targets: { + chrome: 57, + ios: 9, + }, + }, + ], + ], + plugins: [ + ['@babel/plugin-proposal-class-properties'], + ['@babel/plugin-proposal-decorators', { legacy: true }], + ['@babel/plugin-transform-runtime', { regenerator: true }], + ], + }, + }, + ], + }, + { + test: /\.(png|jpe?g|gif)$/i, + use: [{ + loader: 'url-loader', + options: { + // if you would like to use base64 for picture, uncomment limit: true + // limit: true, + fallback: 'file-loader', + name: '[name].[ext]', + outputPath: 'assets/', + }, + }], + }, + { + test: /\.(ts)$/, + use: [ + { + loader: 'ts-loader', + options: { + transpileOnly: true, + appendTsSuffixTo: [/\.vue$/], + }, + }, + ], + exclude: /node_modules/, + }, + { + test: /\.mjs$/, + include: /node_modules/, + type: 'javascript/auto', + }, + ], + }, + resolve: { + extensions: ['.js', '.vue', '.json', '.ts'], + alias: (() => { + const aliases = { + src: path.resolve('./src'), + }; + + // If @vue/runtime-core was built exist in packages directory then make an alias + // Remove the section if you don't use it + const hippyVueRuntimeCorePath = path.resolve(__dirname, '../../../../packages/hippy-vue-next/node_modules/@vue/runtime-core'); + if (fs.existsSync(path.resolve(hippyVueRuntimeCorePath, 'index.js'))) { + console.warn(`* Using the @vue/runtime-core in ${hippyVueRuntimeCorePath} as vue alias`); + aliases['@vue/runtime-core'] = hippyVueRuntimeCorePath; + } else { + console.warn('* Using the @vue/runtime-core defined in package.json'); + } + + // If @hippy/vue-next was built exist in packages directory then make an alias + // Remove the section if you don't use it + const hippyVueNextPath = path.resolve(__dirname, '../../../../packages/hippy-vue-next/dist'); + if (fs.existsSync(path.resolve(hippyVueNextPath, 'index.js'))) { + console.warn(`* Using the @hippy/vue-next in ${hippyVueNextPath} as @hippy/vue-next alias`); + aliases['@hippy/vue-next'] = hippyVueNextPath; + } else { + console.warn('* Using the @hippy/vue-next defined in package.json'); + } + + return aliases; + })(), + }, +}; diff --git a/driver/js/examples/hippy-vue-next-ssr-demo/scripts/webpack-ssr-config/client.entry.js b/driver/js/examples/hippy-vue-next-ssr-demo/scripts/webpack-ssr-config/client.entry.js new file mode 100644 index 00000000000..f710f743a1b --- /dev/null +++ b/driver/js/examples/hippy-vue-next-ssr-demo/scripts/webpack-ssr-config/client.entry.js @@ -0,0 +1,97 @@ +const path = require('path'); +const webpack = require('webpack'); + +const pkg = require('../../package.json'); + +module.exports = { + mode: 'production', + devtool: false, + entry: { + index: path.resolve(pkg.ssrMain), + }, + output: { + filename: '[name].js', + strictModuleExceptionHandling: true, + path: path.resolve('./dist'), + globalObject: '(0, eval)("this")', + }, + plugins: [ + new webpack.DefinePlugin({ + 'process.env': { + NODE_ENV: JSON.stringify('production'), + }, + __VUE_OPTIONS_API__: true, + __VUE_PROD_DEVTOOLS__: false, + __PLATFORM__: null, + }), + ], + module: { + rules: [ + { + test: /\.t|js$/, + use: [{ + loader: 'babel-loader', + options: { + sourceType: 'unambiguous', + presets: [ + [ + '@babel/preset-env', + { + targets: { + chrome: 57, + ios: 9, + }, + }, + ], + ], + plugins: [ + ['@babel/plugin-proposal-class-properties'], + ['@babel/plugin-proposal-decorators', { legacy: true }], + ['@babel/plugin-transform-runtime', { regenerator: true }], + ], + }, + }, + ], + }, + { + test: /\.(png|jpe?g|gif)$/i, + use: [{ + loader: 'url-loader', + options: { + // comment line when production environment + // entry file do not have image asset + limit: true, + // limit: 8192, + // fallback: 'file-loader', + // name: '[name].[ext]', + // outputPath: 'assets/', + }, + }], + }, + { + test: /\.(ts)$/, + use: [ + { + loader: 'ts-loader', + options: { + transpileOnly: true, + appendTsSuffixTo: [/\.vue$/], + }, + }, + ], + exclude: /node_modules/, + }, + { + test: /\.mjs$/, + include: /node_modules/, + type: 'javascript/auto', + }, + ], + }, + resolve: { + extensions: ['.js', '.json', '.ts'], + alias: (() => ({ + src: path.resolve('./src'), + }))(), + }, +}; diff --git a/driver/js/examples/hippy-vue-next-ssr-demo/scripts/webpack-ssr-config/client.ios.js b/driver/js/examples/hippy-vue-next-ssr-demo/scripts/webpack-ssr-config/client.ios.js new file mode 100644 index 00000000000..eec18701059 --- /dev/null +++ b/driver/js/examples/hippy-vue-next-ssr-demo/scripts/webpack-ssr-config/client.ios.js @@ -0,0 +1,3 @@ +const { getWebpackSsrBaseConfig } = require('./client.base'); + +module.exports = getWebpackSsrBaseConfig('ios', 'production'); diff --git a/driver/js/examples/hippy-vue-next-ssr-demo/scripts/webpack-ssr-config/client.ios.vendor.js b/driver/js/examples/hippy-vue-next-ssr-demo/scripts/webpack-ssr-config/client.ios.vendor.js new file mode 100644 index 00000000000..6783d5a86ba --- /dev/null +++ b/driver/js/examples/hippy-vue-next-ssr-demo/scripts/webpack-ssr-config/client.ios.vendor.js @@ -0,0 +1,108 @@ +const fs = require('fs'); +const path = require('path'); +const webpack = require('webpack'); +const { VueLoaderPlugin } = require('vue-loader'); +const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin'); + +const platform = 'ios'; + +module.exports = { + mode: 'production', + bail: true, + entry: { + vendor: [path.resolve(__dirname, '../vendor.js')], + }, + output: { + filename: `[name].${platform}.js`, + path: path.resolve(`./dist/${platform}/`), + globalObject: '(0, eval)("this")', + library: 'hippyVueBase', + }, + plugins: [ + new webpack.NamedModulesPlugin(), + new webpack.DefinePlugin({ + 'process.env.NODE_ENV': JSON.stringify('production'), + __PLATFORM__: JSON.stringify(platform), + }), + new CaseSensitivePathsPlugin(), + new VueLoaderPlugin(), + new webpack.DllPlugin({ + context: path.resolve(__dirname, '../..'), + path: path.resolve(__dirname, `../../dist/${platform}/[name]-manifest.json`), + name: 'hippyVueBase', + }), + ], + module: { + rules: [ + { + test: /\.vue$/, + use: [ + { + loader: 'vue-loader', + options: { + compilerOptions: { + // disable vue3 dom patch flag,because hippy do not support innerHTML + hoistStatic: false, + // whitespace handler, default is 'condense', it can be set 'preserve' + whitespace: 'condense', + }, + }, + }, + ], + }, + { + test: /\.(js)$/, + use: [ + { + loader: 'babel-loader', + options: { + presets: [ + [ + '@babel/preset-env', + { + targets: { + ios: 9, + }, + }, + ], + ], + plugins: [ + ['@babel/plugin-proposal-class-properties'], + ], + }, + }, + ], + }, + ], + }, + resolve: { + extensions: ['.js', '.vue', '.json', '.ts'], + alias: (() => { + const aliases = { + src: path.resolve('./src'), + }; + + // If @vue/runtime-core was built exist in packages directory then make an alias + // Remove the section if you don't use it + const hippyVueRuntimeCorePath = path.resolve(__dirname, '../../../../packages/hippy-vue-next/node_modules/@vue/runtime-core'); + if (fs.existsSync(path.resolve(hippyVueRuntimeCorePath, 'index.js'))) { + console.warn(`* Using the @vue/runtime-core in ${hippyVueRuntimeCorePath} as vue alias`); + aliases['@vue/runtime-core'] = hippyVueRuntimeCorePath; + } else { + console.warn('* Using the @vue/runtime-core defined in package.json'); + } + + // If @hippy/vue-next was built exist in packages directory then make an alias + // Remove the section if you don't use it + const hippyVueNextPath = path.resolve(__dirname, '../../../../packages/hippy-vue-next/dist'); + if (fs.existsSync(path.resolve(hippyVueNextPath, 'index.js'))) { + console.warn(`* Using the @hippy/vue-next in ${hippyVueNextPath} as @hippy/vue-next alias`); + aliases['@hippy/vue-next'] = hippyVueNextPath; + } else { + console.warn('* Using the @hippy/vue-next defined in package.json'); + } + + return aliases; + })(), + }, +}; diff --git a/driver/js/examples/hippy-vue-next-ssr-demo/scripts/webpack-ssr-config/server.dev.js b/driver/js/examples/hippy-vue-next-ssr-demo/scripts/webpack-ssr-config/server.dev.js new file mode 100644 index 00000000000..e82008654ed --- /dev/null +++ b/driver/js/examples/hippy-vue-next-ssr-demo/scripts/webpack-ssr-config/server.dev.js @@ -0,0 +1,186 @@ +const path = require('path'); +const fs = require('fs'); +const webpack = require('webpack'); + +const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin'); +const compilerSSR = require('@hippy/vue-next-compiler-ssr'); +const { VueLoaderPlugin } = require('vue-loader'); +const pkg = require('../../package.json'); + +let cssLoader = '@hippy/vue-css-loader'; +const hippyVueCssLoaderPath = path.resolve(__dirname, '../../../../packages/hippy-vue-css-loader/dist/css-loader.js'); +if (fs.existsSync(hippyVueCssLoaderPath)) { + console.warn(`* Using the @hippy/vue-css-loader in ${hippyVueCssLoaderPath}`); + cssLoader = hippyVueCssLoaderPath; +} else { + console.warn('* Using the @hippy/vue-css-loader defined in package.json'); +} + +let vueNext = '@hippy/vue-next'; +const hippyVueNextPath = path.resolve(__dirname, '../../../../packages/hippy-vue-next/dist/index.js'); +if (fs.existsSync(hippyVueNextPath)) { + console.warn(`* Using the @hippy/vue-next in ${hippyVueNextPath}`); + vueNext = hippyVueNextPath; +} else { + console.warn('* Using the @hippy/vue-next defined in package.json'); +} +const { isNativeTag } = require(vueNext); + + +module.exports = { + mode: 'development', + bail: true, + devtool: 'source-map', + target: 'node', + watch: true, + watchOptions: { + // file changed, rebuild delay time + aggregateTimeout: 1000, + }, + entry: { + index: path.resolve(pkg.serverEntry), + }, + output: { + filename: 'index.js', + strictModuleExceptionHandling: true, + path: path.resolve('dist/server'), + }, + plugins: [ + // only generate one chunk at server side + new webpack.optimize.LimitChunkCountPlugin({ + maxChunks: 1, + }), + new webpack.DefinePlugin({ + 'process.env': { + NODE_ENV: JSON.stringify('development'), + HIPPY_SSR: true, + }, + __VUE_OPTIONS_API__: true, + __VUE_PROD_DEVTOOLS__: false, + }), + new CaseSensitivePathsPlugin(), + new VueLoaderPlugin(), + ], + module: { + rules: [ + { + test: /\.vue$/, + use: [ + { + loader: 'vue-loader', + options: { + compilerOptions: { + // because hippy do not support innerHTML, so we should close this feature + hoistStatic: false, + // whitespace handler, default is 'condense', it can be set 'preserve' + whitespace: 'condense', + // Vue will recognize non-HTML tags as components, so for Hippy native tags, + // Vue needs to be informed to render them as custom elements + isCustomElement: tag => isNativeTag && isNativeTag(tag), + // real used ssr runtime package, render vue node at server side + ssrRuntimeModuleName: '@hippy/vue-next-server-renderer', + // do not generate html comment node + comments: false, + }, + // real used vue compiler + compiler: compilerSSR, + }, + }, + ], + }, + { + test: /\.(le|c)ss$/, + use: [cssLoader, 'less-loader'], + }, + { + test: /\.t|js$/, + use: [ + { + loader: 'babel-loader', + options: { + sourceType: 'unambiguous', + presets: [ + [ + '@babel/preset-env', + { + targets: { + node: '16.0', + }, + }, + ], + ], + plugins: [ + ['@babel/plugin-proposal-nullish-coalescing-operator'], + ], + }, + }, + ], + }, + { + test: /\.(png|jpe?g|gif)$/i, + use: [{ + loader: 'url-loader', + options: { + // if you would like to use base64 for picture, uncomment limit: true + // limit: true, + limit: true, + fallback: 'file-loader', + name: '[name].[ext]', + outputPath: 'assets/', + }, + }], + }, + { + test: /\.(ts)$/, + use: [ + { + loader: 'ts-loader', + options: { + transpileOnly: true, + appendTsSuffixTo: [/\.vue$/], + }, + }, + ], + exclude: /node_modules/, + }, + { + test: /\.mjs$/, + include: /node_modules/, + type: 'javascript/auto', + }, + ], + }, + resolve: { + extensions: ['.js', '.vue', '.json', '.ts'], + alias: (() => { + const aliases = { + src: path.resolve('./src'), + }; + + // If @vue/runtime-core was built exist in packages directory then make an alias + // Remove the section if you don't use it + const hippyVueRuntimeCorePath = path.resolve(__dirname, '../../../../packages/hippy-vue-next/node_modules/@vue/runtime-core'); + if (fs.existsSync(path.resolve(hippyVueRuntimeCorePath, 'index.js'))) { + console.warn(`* Using the @vue/runtime-core in ${hippyVueRuntimeCorePath} as vue alias`); + aliases['@vue/runtime-core'] = hippyVueRuntimeCorePath; + } else { + console.warn('* Using the @vue/runtime-core defined in package.json'); + } + + // If @hippy/vue-next was built exist in packages directory then make an alias + // Remove the section if you don't use it + const hippyVueNextPath = path.resolve(__dirname, '../../../../packages/hippy-vue-next/dist'); + if (fs.existsSync(path.resolve(hippyVueNextPath, 'index.js'))) { + console.warn(`* Using the @hippy/vue-next in ${hippyVueNextPath} as @hippy/vue-next alias`); + aliases['@hippy/vue-next'] = hippyVueNextPath; + } else { + console.warn('* Using the @hippy/vue-next defined in package.json'); + } + + return aliases; + })(), + }, + externals: { + express: 'commonjs express', // this line is just to use the express dependency in a commonjs way + }, +}; diff --git a/driver/js/examples/hippy-vue-next-ssr-demo/scripts/webpack-ssr-config/server.entry.js b/driver/js/examples/hippy-vue-next-ssr-demo/scripts/webpack-ssr-config/server.entry.js new file mode 100644 index 00000000000..3ecee4aea93 --- /dev/null +++ b/driver/js/examples/hippy-vue-next-ssr-demo/scripts/webpack-ssr-config/server.entry.js @@ -0,0 +1,177 @@ +const path = require('path'); +const fs = require('fs'); +const webpack = require('webpack'); + +const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin'); +const compilerSSR = require('@hippy/vue-next-compiler-ssr'); +const { VueLoaderPlugin } = require('vue-loader'); +const pkg = require('../../package.json'); + +let cssLoader = '@hippy/vue-css-loader'; +const hippyVueCssLoaderPath = path.resolve(__dirname, '../../../../packages/hippy-vue-css-loader/dist/css-loader.js'); +if (fs.existsSync(hippyVueCssLoaderPath)) { + console.warn(`* Using the @hippy/vue-css-loader in ${hippyVueCssLoaderPath}`); + cssLoader = hippyVueCssLoaderPath; +} else { + console.warn('* Using the @hippy/vue-css-loader defined in package.json'); +} + +let vueNext = '@hippy/vue-next'; +const hippyVueNextPath = path.resolve(__dirname, '../../../../packages/hippy-vue-next/dist/index.js'); +if (fs.existsSync(hippyVueNextPath)) { + console.warn(`* Using the @hippy/vue-next in ${hippyVueNextPath}`); + vueNext = hippyVueNextPath; +} else { + console.warn('* Using the @hippy/vue-next defined in package.json'); +} +const { isNativeTag } = require(vueNext); + +module.exports = { + mode: 'production', + bail: true, + devtool: false, + target: 'node', + entry: { + index: path.resolve(pkg.serverEntry), + }, + output: { + filename: 'index.js', + strictModuleExceptionHandling: true, + path: path.resolve('dist/server'), + }, + plugins: [ + // only generate one chunk at server side + new webpack.optimize.LimitChunkCountPlugin({ + maxChunks: 1, + }), + new webpack.DefinePlugin({ + 'process.env': { + NODE_ENV: JSON.stringify('production'), + HIPPY_SSR: true, + }, + __VUE_OPTIONS_API__: true, + __VUE_PROD_DEVTOOLS__: false, + }), + new CaseSensitivePathsPlugin(), + new VueLoaderPlugin(), + ], + module: { + rules: [ + { + test: /\.vue$/, + use: [ + { + loader: 'vue-loader', + options: { + compilerOptions: { + // because hippy do not support innerHTML, so we should close this feature + hoistStatic: false, + // whitespace handler, default is 'condense', it can be set 'preserve' + whitespace: 'condense', + // Vue will recognize non-HTML tags as components, so for Hippy native tags, + // Vue needs to be informed to render them as custom elements + isCustomElement: tag => isNativeTag && isNativeTag(tag), + // real used ssr runtime package, render vue node at server side + ssrRuntimeModuleName: '@hippy/vue-next-server-renderer', + // do not generate html comment node + comments: false, + }, + // real used vue compiler + compiler: compilerSSR, + }, + }, + ], + }, + { + test: /\.(le|c)ss$/, + use: [cssLoader, 'less-loader'], + }, + { + test: /\.t|js$/, + use: [ + { + loader: 'babel-loader', + options: { + sourceType: 'unambiguous', + presets: [ + [ + '@babel/preset-env', + { + targets: { + node: '16.0', + }, + }, + ], + ], + plugins: [ + ['@babel/plugin-proposal-nullish-coalescing-operator'], + ], + }, + }, + ], + }, + { + test: /\.(png|jpe?g|gif)$/i, + use: [{ + loader: 'url-loader', + options: { + // if you would like to use base64 for picture, uncomment limit: true + // limit: true, + limit: 8192, + fallback: 'file-loader', + name: '[name].[ext]', + outputPath: 'assets/', + }, + }], + }, + { + test: /\.(ts)$/, + use: [ + { + loader: 'ts-loader', + options: { + transpileOnly: true, + appendTsSuffixTo: [/\.vue$/], + }, + }, + ], + exclude: /node_modules/, + }, + { + test: /\.mjs$/, + include: /node_modules/, + type: 'javascript/auto', + }, + ], + }, + resolve: { + extensions: ['.js', '.vue', '.json', '.ts'], + alias: (() => { + const aliases = { + src: path.resolve('./src'), + }; + + // If @vue/runtime-core was built exist in packages directory then make an alias + // Remove the section if you don't use it + const hippyVueRuntimeCorePath = path.resolve(__dirname, '../../../../packages/hippy-vue-next/node_modules/@vue/runtime-core'); + if (fs.existsSync(path.resolve(hippyVueRuntimeCorePath, 'index.js'))) { + console.warn(`* Using the @vue/runtime-core in ${hippyVueRuntimeCorePath} as vue alias`); + aliases['@vue/runtime-core'] = hippyVueRuntimeCorePath; + } else { + console.warn('* Using the @vue/runtime-core defined in package.json'); + } + + // If @hippy/vue-next was built exist in packages directory then make an alias + // Remove the section if you don't use it + const hippyVueNextPath = path.resolve(__dirname, '../../../../packages/hippy-vue-next/dist'); + if (fs.existsSync(path.resolve(hippyVueNextPath, 'index.js'))) { + console.warn(`* Using the @hippy/vue-next in ${hippyVueNextPath} as @hippy/vue-next alias`); + aliases['@hippy/vue-next'] = hippyVueNextPath; + } else { + console.warn('* Using the @hippy/vue-next defined in package.json'); + } + + return aliases; + })(), + }, +}; diff --git a/driver/js/examples/hippy-vue-next-ssr-demo/scripts/webpack.ssr.build.js b/driver/js/examples/hippy-vue-next-ssr-demo/scripts/webpack.ssr.build.js new file mode 100644 index 00000000000..fe0210af821 --- /dev/null +++ b/driver/js/examples/hippy-vue-next-ssr-demo/scripts/webpack.ssr.build.js @@ -0,0 +1,109 @@ +/** + * build js script for ssr production + */ +const { arch } = require('os'); +const { exec, rm, cp } = require('shelljs'); + +let envPrefixStr = 'cross-env-os os="Windows_NT,Linux,Darwin" minVersion=17 NODE_OPTIONS=--openssl-legacy-provider'; +const isArmCpu = arch() + .toLowerCase() + .includes('arm'); +if (isArmCpu) { + envPrefixStr = ''; +} + +/** + * get executed script + * + * @param configFile - config file name + */ +function getScriptCommand(configFile) { + return `${envPrefixStr} webpack --config scripts/webpack-ssr-config/${configFile} --mode production`; +} + +/** + * execute script + * + * @param scriptStr - script content + * @param options - shelljs options + */ +function runScript(scriptStr, options = { silent: false }) { + const result = exec(scriptStr, options); + if (result.code !== 0) { + console.error(`❌ execute cmd - "${scriptStr}" error: ${result.stderr}`); + process.exit(1); + } +} + +/** + * build ssr client entry bundle + */ +function buildServerEntry() { + // build server entry + runScript(getScriptCommand('server.entry.js')); +} + +/** + * build ssr sever and client bundle + */ +function buildJsBundle() { + // build Android client bundle + runScript(getScriptCommand('client.android.js')); + // build iOS client bundle + runScript(getScriptCommand('client.ios.js')); + // 3. build client entry + runScript(getScriptCommand('client.entry.js')); +} + +/** + * build js vendor for production + */ +function buildJsVendor() { + // ios + runScript(getScriptCommand('client.ios.vendor.js')); + // android + runScript(getScriptCommand('client.android.vendor.js')); +} + +/** + * generate client entry js bundle for production + */ +function generateClientEntryForProduction() { + // copy js entry to every platform + // ios + cp('-f', './dist/index.js', './dist/ios/index.ios.js'); + // android + cp('-f', './dist/index.js', './dist/android/index.android.js'); +} + +/** + * copy generated files to native demo + */ +function copyFilesToNativeDemo() { + cp('-Rf', './dist/ios/*', '../ios-demo/res/'); // Update the ios demo project + cp('-Rf', './dist/android/*', '../android-demo/res/'); // # Update the android project +} + +/** + * build production bundle + */ +function buildProduction() { + // production, build all entry bundle, ssr server should execute by user + // first, remove dist directory + rm('-rf', './dist'); + // second, build js vendor + buildJsVendor(); + // third, build all js bundle + buildJsBundle(); + // fourth, build client entry + buildServerEntry(); + // fifth, build every platform's client entry + generateClientEntryForProduction(); + // last, copy all files to native demo + copyFilesToNativeDemo(); +} + +// build production bundle +buildProduction(); + + diff --git a/driver/js/examples/hippy-vue-next-ssr-demo/scripts/webpack.ssr.dev.js b/driver/js/examples/hippy-vue-next-ssr-demo/scripts/webpack.ssr.dev.js new file mode 100644 index 00000000000..4c225478b6f --- /dev/null +++ b/driver/js/examples/hippy-vue-next-ssr-demo/scripts/webpack.ssr.dev.js @@ -0,0 +1,39 @@ +/** + * build script for ssr + */ + +const webpack = require('webpack'); +const { exec } = require('shelljs'); +const serverConfig = require('./webpack-ssr-config/server.dev'); + +const compiler = webpack(serverConfig); +let childProcess = null; + +/** + * execute script + * + * @param scriptStr - script content + * @param options - shelljs options + */ +function runScript(scriptStr, options) { + if (childProcess) { + // kill process first + childProcess.kill(); + } + childProcess = exec(scriptStr, options, (code, stdout, stderr) => { + if (code) { + console.error(`❌ execute cmd - "${scriptStr}" error: ${stderr}`); + process.exit(1); + } + }); +} + +compiler.hooks.done.tap('DonePlugin', () => { + // restart node process after build success + setTimeout(() => { + runScript('node ./dist/server/index.js', { async: true, silent: false }); + }, 0); +}); + +// watch server entry change +compiler.watch({}, () => {}); diff --git a/driver/js/examples/hippy-vue-next-ssr-demo/server.ts b/driver/js/examples/hippy-vue-next-ssr-demo/server.ts new file mode 100644 index 00000000000..d92fd6ae8ca --- /dev/null +++ b/driver/js/examples/hippy-vue-next-ssr-demo/server.ts @@ -0,0 +1,83 @@ +import express from 'express'; +import { render, HIPPY_GLOBAL_STYLE_NAME } from 'src/main-server'; + +interface MinifiedStyleDeclaration { + [key: number]: number | string; +} + +/** + * minify css content + */ +function minifyStyleContent(rawStyleContent): NeedToTyped[] | MinifiedStyleDeclaration[] { + if (rawStyleContent?.length && Array.isArray(rawStyleContent)) { + const minifiedStyle: MinifiedStyleDeclaration[] = []; + rawStyleContent.forEach((styleContent) => { + // minified style is array, 0 index is selectors, 1 index is declaration, no hash + minifiedStyle.push([ + styleContent.selectors, + // minify declarations + styleContent.declarations.map(declaration => [declaration.property, declaration.value]), + ]); + }); + return minifiedStyle; + } + + return rawStyleContent; +} + +/** + * get ssr style content + * + * @param globalStyleName - hippy global style name + */ +function getSsrStyleContent(globalStyleName): NeedToTyped[] { + if (global.ssrStyleContentList) { + return global.ssrStyleContentList; + } + // cache global style sheet, then non first request could return directly, unnecessary to + // serialize again + global.ssrStyleContentList = JSON.stringify(minifyStyleContent(global[globalStyleName])); + + return global.ssrStyleContentList; +} + +// server listen port +const serverPort = 8080; +// init http server +const server = express(); +// use json middleware +server.use(express.json()); + +// listen request +server.all('/getSsrFirstScreenData', (req, rsp) => { + // get hippy ssr node list and other const + render('/', { + appName: 'Demo', + iPhone: { + statusBar: { disabled: true }, + }, + }, req.body).then(({ + list, + store, + uniqueId, + }) => { + // send response + rsp.json({ + code: 0, + data: list, + store: store.state.value, + uniqueId, + styleContent: getSsrStyleContent(HIPPY_GLOBAL_STYLE_NAME), + }); + }) + .catch((error) => { + rsp.json({ + code: -1, + message: `get ssr data error: ${JSON.stringify(error)}`, + }); + }); +}); + +// start server +server.listen(serverPort); +console.log(`Server listen on:${serverPort}`); diff --git a/driver/js/examples/hippy-vue-next-ssr-demo/src/app.vue b/driver/js/examples/hippy-vue-next-ssr-demo/src/app.vue new file mode 100644 index 00000000000..bb96e1b1097 --- /dev/null +++ b/driver/js/examples/hippy-vue-next-ssr-demo/src/app.vue @@ -0,0 +1,223 @@ + + + diff --git a/driver/js/examples/hippy-vue-next-ssr-demo/src/assets/defaultSource.jpg b/driver/js/examples/hippy-vue-next-ssr-demo/src/assets/defaultSource.jpg new file mode 100644 index 00000000000..833417ea2fb Binary files /dev/null and b/driver/js/examples/hippy-vue-next-ssr-demo/src/assets/defaultSource.jpg differ diff --git a/driver/js/examples/hippy-vue-next-ssr-demo/src/assets/hippyLogoWhite.png b/driver/js/examples/hippy-vue-next-ssr-demo/src/assets/hippyLogoWhite.png new file mode 100644 index 00000000000..20e428e2fa9 Binary files /dev/null and b/driver/js/examples/hippy-vue-next-ssr-demo/src/assets/hippyLogoWhite.png differ diff --git a/driver/js/examples/hippy-vue-next-ssr-demo/src/assets/logo.png b/driver/js/examples/hippy-vue-next-ssr-demo/src/assets/logo.png new file mode 100644 index 00000000000..f3d2503fc2a Binary files /dev/null and b/driver/js/examples/hippy-vue-next-ssr-demo/src/assets/logo.png differ diff --git a/driver/js/examples/hippy-vue-next-ssr-demo/src/back-icon.png b/driver/js/examples/hippy-vue-next-ssr-demo/src/back-icon.png new file mode 100644 index 00000000000..fe1fbf1cd10 Binary files /dev/null and b/driver/js/examples/hippy-vue-next-ssr-demo/src/back-icon.png differ diff --git a/driver/js/examples/hippy-vue-next-ssr-demo/src/components/demo/demo-button.vue b/driver/js/examples/hippy-vue-next-ssr-demo/src/components/demo/demo-button.vue new file mode 100644 index 00000000000..00c06a74eb8 --- /dev/null +++ b/driver/js/examples/hippy-vue-next-ssr-demo/src/components/demo/demo-button.vue @@ -0,0 +1,130 @@ + + + + + diff --git a/driver/js/examples/hippy-vue-next-ssr-demo/src/components/demo/demo-div.vue b/driver/js/examples/hippy-vue-next-ssr-demo/src/components/demo/demo-div.vue new file mode 100644 index 00000000000..f0a3aa4ea1e --- /dev/null +++ b/driver/js/examples/hippy-vue-next-ssr-demo/src/components/demo/demo-div.vue @@ -0,0 +1,324 @@ + + + + + diff --git a/driver/js/examples/hippy-vue-next-ssr-demo/src/components/demo/demo-dynamicimport.vue b/driver/js/examples/hippy-vue-next-ssr-demo/src/components/demo/demo-dynamicimport.vue new file mode 100644 index 00000000000..1b231d3702e --- /dev/null +++ b/driver/js/examples/hippy-vue-next-ssr-demo/src/components/demo/demo-dynamicimport.vue @@ -0,0 +1,101 @@ + + + + + diff --git a/driver/js/examples/hippy-vue-next-ssr-demo/src/components/demo/demo-iframe.vue b/driver/js/examples/hippy-vue-next-ssr-demo/src/components/demo/demo-iframe.vue new file mode 100644 index 00000000000..36a57aeeae0 --- /dev/null +++ b/driver/js/examples/hippy-vue-next-ssr-demo/src/components/demo/demo-iframe.vue @@ -0,0 +1,126 @@ +