├── .gitignore
├── scripts
├── template
│ ├── page.ttml
│ ├── utils.wxs
│ └── base.ttml
└── index.js
├── dist
├── pages
│ ├── page-entry
│ │ ├── index.ttml
│ │ └── index.ttss
│ └── page-second
│ │ ├── index.ttml
│ │ └── index.js
├── project.config.json
├── app.json
├── runtime.js.map
├── runtime.js
├── base.ttml
└── app.js
├── .prettierrc.js
├── src
├── index.ts
├── short-cut.ts
├── hooks.ts
├── util.ts
├── render.ts
├── native-components.ts
├── interface.ts
├── hydrate.ts
├── createPageConfig.ts
├── createAppConfig.ts
├── host-config.ts
└── taro-element.ts
├── .editorconfig
├── SUPPORT.md
├── demo
├── project.config.json
├── app.config.json
├── pages
│ ├── page-entry
│ │ ├── index.css
│ │ └── index.tsx
│ └── page-second
│ │ └── index.tsx
├── app.ts
├── util.ts
└── component
│ └── test-comp.tsx
├── global.d.ts
├── package.json
├── tsconfig.json
├── README.md
├── webpack.config.js
└── LICENSE
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .vscode/
3 | yarn.lock
--------------------------------------------------------------------------------
/scripts/template/page.ttml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/dist/pages/page-entry/index.ttml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/dist/pages/page-second/index.ttml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | singleQuote: true,
3 | jsxSingleQuote: false,
4 | trailingComma: 'all',
5 | semi: false,
6 | }
7 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export { createPageConfig } from './createPageConfig'
2 | export { createAppConfig } from './createAppConfig'
3 | export * from './hooks'
4 | export * from './native-components'
5 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | insert_final_newline = true
7 |
8 | [*.{css,less,js,jsx,ts,tsx}]
9 | indent_size = 2
10 | indent_style = space
11 |
--------------------------------------------------------------------------------
/SUPPORT.md:
--------------------------------------------------------------------------------
1 | ## 支持头条/微信小程序之外的小程序
2 | #### 1. 修改webpack.config.js
3 | (1)修改globalObject和fileType改为对应小程序配置
4 |
5 | (2)修改CopyPlugin插件配置,将对应小程序Taro的base模版拷贝到产物
6 | #### 2. Taro的base模版
7 | 需在Taro构建的小程序项目中,在产物中获取base.xxxx类似模版文件,将模版文件通过CopyPlugin插件,按路径拷贝到产物即可。
8 |
--------------------------------------------------------------------------------
/scripts/index.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const path = require('path')
3 |
4 | const filePath = path.resolve('./dist/app.js')
5 |
6 | const content = fs.readFileSync(filePath)
7 |
8 | fs.writeFileSync(filePath, `require('./vendors.js');\nrequire('./runtime.js');\n\n` + content)
9 |
10 |
--------------------------------------------------------------------------------
/src/short-cut.ts:
--------------------------------------------------------------------------------
1 |
2 | export const RootName = 'root'
3 |
4 | // 这里使用Taro的缩写
5 | export const enum Short {
6 | Container = "container",
7 | Childnodes = "cn",
8 | Text = "v",
9 | NodeType = "nt",
10 | NodeName = "nn",
11 | Style = "st",
12 | Class = "cl",
13 | Src = "src"
14 | }
15 |
--------------------------------------------------------------------------------
/demo/project.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "miniprogramRoot": ".",
3 | "projectname": "mini-taro",
4 | "description": "mini-taro小程序",
5 | "appid": "cli_1234567890abc",
6 | "setting": {
7 | "urlCheck": true,
8 | "es6": false,
9 | "postcss": false,
10 | "minified": false
11 | },
12 | "compileType": "miniprogram"
13 | }
14 |
--------------------------------------------------------------------------------
/dist/project.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "miniprogramRoot": ".",
3 | "projectname": "mini-taro",
4 | "description": "mini-taro小程序",
5 | "appid": "cli_1234567890abc",
6 | "setting": {
7 | "urlCheck": true,
8 | "es6": false,
9 | "postcss": false,
10 | "minified": false
11 | },
12 | "compileType": "miniprogram"
13 | }
14 |
--------------------------------------------------------------------------------
/src/hooks.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * 这里实现的hook对应Taro提供的hook
3 | * 参考这些hook可以实现出任意需要的业务hook,比如useShareAppMessage,usePageScroll等
4 | */
5 | import { taroHook } from "./createPageConfig";
6 |
7 | export const useReady = taroHook('onReady')
8 |
9 | export const useDidShow = taroHook('onShow')
10 |
11 | export const useDidHide = taroHook('onHide')
12 |
--------------------------------------------------------------------------------
/dist/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "pages": [
3 | "pages/page-entry/index",
4 | "pages/page-second/index"
5 | ],
6 | "window": {
7 | "backgroundTextStyle": "light",
8 | "navigationBarBackgroundColor": "#fff",
9 | "backgroundColor": "#f5f6f7",
10 | "navigationBarTextStyle": "black"
11 | },
12 | "entryPagePath": "pages/page-entry/index"
13 | }
14 |
--------------------------------------------------------------------------------
/demo/app.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "pages": [
3 | "pages/page-entry/index",
4 | "pages/page-second/index"
5 | ],
6 | "window": {
7 | "backgroundTextStyle": "light",
8 | "navigationBarBackgroundColor": "#fff",
9 | "backgroundColor": "#f5f6f7",
10 | "navigationBarTextStyle": "black"
11 | },
12 | "entryPagePath": "pages/page-entry/index"
13 | }
14 |
--------------------------------------------------------------------------------
/demo/pages/page-entry/index.css:
--------------------------------------------------------------------------------
1 | .wrapper {
2 | color: black;
3 | margin: 10px 10px;
4 | text-align: center;
5 | }
6 |
7 | .inline-block {
8 | display: inline-flex;
9 | justify-content: space-between;
10 | width: 100%;
11 | margin: 10px;
12 | }
13 |
14 | .class-sample {
15 | color: #f98e8b;
16 | font-size: 25px;
17 | font-weight: 600;
18 | margin-left: 10px;
19 | }
20 |
--------------------------------------------------------------------------------
/global.d.ts:
--------------------------------------------------------------------------------
1 |
2 | declare function Page(params: any): unknown
3 |
4 | declare function App(params: any): unknown
5 |
6 | // 字节
7 | declare const tt: any
8 | // 微信
9 | declare const wx: any
10 | // 百度
11 | declare const swan: any
12 | // QQ
13 | declare const qq: any
14 | // 京东
15 | declare const jd: any
16 | // alipay
17 | declare const my: any
18 |
19 | declare function getApp(): unknown
20 |
21 | declare function getCurrentPages(): any[]
22 |
--------------------------------------------------------------------------------
/src/util.ts:
--------------------------------------------------------------------------------
1 | import { NodeType } from "./interface"
2 |
3 | let instanceId: number = 0
4 |
5 | export function reset() {
6 | instanceId = 0
7 | }
8 |
9 | export function generate() {
10 | const id = instanceId
11 | instanceId += 1
12 | return id
13 | }
14 |
15 | export const noop = () => {}
16 |
17 | export const NodeTypeMap: any = {
18 | 'view': NodeType.VIEW,
19 | 'text': NodeType.TEXT,
20 | 'root': NodeType.ROOT
21 | }
22 |
23 | export const isFunction = (target: unknown) => typeof target === 'function'
24 |
--------------------------------------------------------------------------------
/demo/app.ts:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export default class AppComp extends React.Component {
4 | componentDidMount() {
5 | // ...
6 | }
7 |
8 | onLaunch(options: unknown) {
9 | console.warn('App onlaunch', options)
10 | }
11 |
12 | componentDidShow() {
13 | // ...
14 | }
15 |
16 | componentDidHide() {
17 | // ...
18 | }
19 |
20 | componentDidCatchError() {
21 | // ...
22 | }
23 |
24 | render() {
25 | return React.createElement('view', {}, this.props.children)
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/demo/util.ts:
--------------------------------------------------------------------------------
1 | // tt/wx/qq等顶层对象处理
2 |
3 | export const native = ck1() || ck2() || ck3() || ck4() || ck5() || ck6() || error()
4 |
5 | function ck1() {try{return tt}catch(e){return undefined}}
6 | function ck2() {try{return wx}catch(e){return undefined}}
7 | function ck3() {try{return qq}catch(e){return undefined}}
8 | function ck4() {try{return jd}catch(e){return undefined}}
9 | function ck5() {try{return my}catch(e){return undefined}}
10 | function ck6() {try{return swan}catch(e){return undefined}}
11 | function error() { throw new Error('未支持的小程序类型') }
12 |
--------------------------------------------------------------------------------
/src/render.ts:
--------------------------------------------------------------------------------
1 | // 这边要借助 react-reconciler 实现一套虚拟dom树的系统
2 |
3 | import React, { ReactNode } from 'react'
4 | import { TaroReconciler } from './host-config'
5 | import { TaroElement } from './taro-element'
6 |
7 |
8 | const render = (component: ReactNode, container: TaroElement) => {
9 |
10 | if (!container._rootContainer) {
11 | container._rootContainer = TaroReconciler.createContainer(container, 0, false, null);
12 | }
13 |
14 | TaroReconciler.updateContainer(component, container._rootContainer, null)
15 |
16 | return TaroReconciler.getPublicRootInstance(container._rootContainer)
17 | }
18 |
19 | export { render }
20 |
--------------------------------------------------------------------------------
/demo/component/test-comp.tsx:
--------------------------------------------------------------------------------
1 | // 普通function组件
2 |
3 | import React, { FC, useEffect, useState } from 'react'
4 | import { View, Text } from '@/index'
5 |
6 | const TestComp: FC<{}> = () => {
7 | const [content, set] = useState('(准备更新function组件)')
8 | useEffect(() => {
9 | // 2s 后更新数字
10 | const t1 = setTimeout(() => {
11 | set('(更新成功)')
12 | }, 2000)
13 | return () => {
14 | clearTimeout(t1)
15 | }
16 | }, [])
17 |
18 | return
19 | 这里是function组件
20 | {content}
21 |
22 | }
23 |
24 | export default TestComp
25 |
--------------------------------------------------------------------------------
/dist/pages/page-entry/index.ttss:
--------------------------------------------------------------------------------
1 | /*!***********************************************************************************!*\
2 | !*** css ./node_modules/css-loader/dist/cjs.js!./demo/pages/page-entry/index.css ***!
3 | \***********************************************************************************/
4 | .wrapper {
5 | color: black;
6 | margin: 10px 10px;
7 | text-align: center;
8 | }
9 |
10 | .inline-block {
11 | display: inline-flex;
12 | justify-content: space-between;
13 | width: 100%;
14 | margin: 10px;
15 | }
16 |
17 | .class-sample {
18 | color: #f98e8b;
19 | font-size: 25px;
20 | font-weight: 600;
21 | margin-left: 10px;
22 | }
23 |
24 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mini-taro",
3 | "version": "1.0.0",
4 | "main": "index.js",
5 | "license": "MIT",
6 | "scripts": {
7 | "build": "npm run clean && webpack && node ./scripts/index.js",
8 | "clean": "rimraf dist"
9 | },
10 | "devDependencies": {
11 | "@types/react": "^17.0.38",
12 | "@types/react-reconciler": "^0.26.4",
13 | "copy-webpack-plugin": "^10.2.0",
14 | "css-loader": "^6.5.1",
15 | "mini-css-extract-plugin": "^2.5.2",
16 | "rimraf": "^3.0.2",
17 | "ts-loader": "^9.2.6",
18 | "typescript": "^4.5.4",
19 | "webpack": "^5.65.0",
20 | "webpack-cli": "^4.9.1"
21 | },
22 | "dependencies": {
23 | "react": "17.0.2",
24 | "react-reconciler": "0.26.2"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/native-components.ts:
--------------------------------------------------------------------------------
1 | // 基础组件实现
2 | // 原生组件实际就是react组件,名称上与小程序组件对齐
3 |
4 | import React, { CSSProperties } from 'react'
5 |
6 | type Func = (...params: any[]) => void;
7 |
8 | const createNativeComponent = (name: string, props?: Record) => {
9 | return (params: { children?: any; className?: string; style?: CSSProperties; onClick?: Func; type?: string; onInput?: Func; }) => {
10 | const { children, ...nextParams } = params || {}
11 | return React.createElement(name, { ...nextParams }, children)
12 | }
13 | }
14 |
15 | export const View = createNativeComponent('view', {})
16 |
17 | export const Text = createNativeComponent('text', {})
18 |
19 | export const Button = createNativeComponent('button', {})
20 |
21 | export const Input = createNativeComponent('input', {})
22 |
23 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2017",
4 | "module": "commonjs",
5 | "removeComments": false,
6 | "preserveConstEnums": true,
7 | "moduleResolution": "node",
8 | "emitDecoratorMetadata": true,
9 | "experimentalDecorators": true,
10 | "noImplicitAny": true,
11 | "allowSyntheticDefaultImports": true,
12 | "esModuleInterop": true,
13 | "outDir": "lib",
14 | "noUnusedLocals": false,
15 | "noUnusedParameters": false,
16 | "strictNullChecks": true,
17 | "strictFunctionTypes": true,
18 | "sourceMap": true,
19 | "baseUrl": ".",
20 | "rootDir": ".",
21 | "jsx": "react",
22 | "jsxFactory": "React.createElement",
23 | "allowJs": true,
24 | "resolveJsonModule": true,
25 | "typeRoots": [
26 | "node_modules/@types",
27 | "global.d.ts"
28 | ],
29 | "paths": {
30 | "@/*": ["./src/*"]
31 | }
32 | },
33 | "exclude": [
34 | "node_modules",
35 | "dist"
36 | ],
37 | "compileOnSave": false
38 | }
--------------------------------------------------------------------------------
/demo/pages/page-second/index.tsx:
--------------------------------------------------------------------------------
1 | // class组件页面
2 |
3 | import React from 'react'
4 | import { View, Text, Button } from '@/index'
5 | import TestFunctionComp from '../../component/test-comp'
6 | import { native } from '../../util'
7 |
8 | export class SecondPage extends React.Component {
9 |
10 | constructor(props: Record) {
11 | super(props)
12 | this.state = {
13 | text: '你好呀,这里是第二个页面!'
14 | }
15 | }
16 |
17 | public changeText() {
18 | this.setState({ text: '今天是好天气' })
19 | }
20 |
21 | public returnEntry() {
22 | native.navigateBack()
23 | }
24 |
25 | render() {
26 | return
27 | {this.state.text}
28 |
29 |
30 |
31 |
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/interface.ts:
--------------------------------------------------------------------------------
1 | // import { VNode } from "./vnode"
2 |
3 | import { Short } from "./short-cut"
4 |
5 | enum NodeType {
6 | ROOT,
7 | TEXT,
8 | VIEW
9 | }
10 |
11 | type Props = Record
12 |
13 |
14 | export interface MpInstance {
15 | config: Record
16 | setData: (data: unknown, cb: () => void) => void
17 | $taroParams?: Record
18 | $taroPath: string
19 | data: any
20 | selectComponent: (selector: string) => any
21 | }
22 |
23 | export type UpdatePayloadValue = string | boolean | MiniData | MiniData[]
24 | export type DataTree = Record
25 |
26 | export interface UpdatePayload {
27 | path: string;
28 | value: UpdatePayloadValue
29 | }
30 |
31 | export interface MiniElementData {
32 | [Short.Childnodes]?: MiniData[]
33 | [Short.NodeName]: string
34 | [Short.Class]?: string
35 | [Short.Style]?: string
36 | uid: string
37 | [key: string]: unknown
38 | }
39 |
40 | export interface MiniTextData {
41 | [Short.Text]: string
42 | [Short.NodeName]: string
43 | }
44 |
45 | export type MiniData = MiniElementData | MiniTextData
46 |
47 | export type NodeName = 'view' | 'text' | '#text' | 'button'
48 |
49 | export {
50 | NodeType,
51 | Props,
52 | }
53 |
--------------------------------------------------------------------------------
/scripts/template/utils.wxs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | a: function (l, n, s) {
3 | var a = ["view","catch-view","cover-view","static-view","pure-view","block","text","static-text","slot","slot-view","label","form","scroll-view","swiper","swiper-item","native-modal","native-switch","selector","reaction-panel","native-dialog","native-loading","native-button","native-fab","native-tab","native-tabs"]
4 | var b = ["static-text","slot","slot-view","label","form","scroll-view","swiper","swiper-item"]
5 | if (a.indexOf(n) === -1) {
6 | l = 0
7 | }
8 | if (b.indexOf(n) > -1) {
9 | var u = s.split(',')
10 | var depth = 0
11 | for (var i = 0; i < u.length; i++) {
12 | if (u[i] === n) depth++
13 | }
14 | l = depth
15 | }
16 | return 'tmpl_' + l + '_' + n
17 | },
18 | b: function (a, b) {
19 | return a === undefined ? b : a
20 | },
21 | c: function(i, prefix) {
22 | var s = i.focus !== undefined ? 'focus' : 'blur'
23 | return prefix + i.nn + '_' + s
24 | },
25 | d: function (i, v) {
26 | return i === undefined ? v : i
27 | },
28 | e: function (n) {
29 | return 'tmpl_' + n + '_container'
30 | },
31 | f: function (l, n) {
32 | var b = ["static-text","slot","slot-view","label","form","scroll-view","swiper","swiper-item"]
33 | if (b.indexOf(n) > -1) {
34 | if (l) l += ','
35 | l += n
36 | }
37 | return l
38 | }
39 | }
--------------------------------------------------------------------------------
/src/hydrate.ts:
--------------------------------------------------------------------------------
1 | import { MiniData, NodeType } from "./interface"
2 | import { Short } from "./short-cut"
3 | import { TaroElement } from "./taro-element"
4 |
5 | const isEmpty = (children: any[] | undefined) => {
6 | return !children || (Array.isArray(children) && children.length === 0)
7 | }
8 |
9 | export const generateUid = (id: number) => `u-${id}`
10 |
11 | /**
12 | *
13 | * 这个函数是将虚拟dom树转为了渲染属性树
14 | * @param node
15 | * @returns
16 | */
17 | export const hydrate = (node: TaroElement): MiniData => {
18 | if (node.type === NodeType.TEXT && isEmpty(node.children)) {
19 | return {
20 | [Short.Text]: node.text || '',
21 | [Short.NodeName]: node.nodeName,
22 | }
23 | } else {
24 | const { nodeName, props, id, children } = node
25 | const { className, style, children: _children, ...nextProps } = props || {}
26 |
27 | // style字符串化 + 驼峰转横线
28 | let styleContent = ''
29 | if (!!style) {
30 | for (const [key, value] of Object.entries(style as Object)) {
31 | styleContent += `${styleTransform(key)}: ${value};`
32 | }
33 | }
34 |
35 | return {
36 | [Short.Childnodes]: !!children && Array.isArray(children) ? children.map(hydrate) : [],
37 | [Short.NodeName]: nodeName,
38 | [Short.Class]: className as string || '',
39 | [Short.Style]: styleContent as string,
40 | uid: generateUid(id),
41 | ...nextProps,
42 | }
43 | }
44 | }
45 |
46 | // fontSize -> font-size
47 | export const styleTransform = (name: string) => {
48 | const list = name.split('')
49 | const index = list.findIndex(p => /[A-Z]/.test(p))
50 | if (index >= 0) list.splice(index, 1, '-' + list[index].toLowerCase())
51 | return list.join('')
52 | }
53 |
--------------------------------------------------------------------------------
/demo/pages/page-entry/index.tsx:
--------------------------------------------------------------------------------
1 | // function组件页面的实例
2 |
3 | import React, { FC, useState, useEffect } from 'react'
4 | import { View, Text, Button, Input, useReady, useDidHide, useDidShow } from '@/index'
5 | import { native } from '../../util'
6 |
7 | import './index.css'
8 |
9 | export const EntryPage: FC = () => {
10 |
11 | const [name, setName] = useState('')
12 | const [count, setCount] = useState(0)
13 | const [list, setList] = useState([
14 | { key: 1, value: '这是第1个' },
15 | { key: 2, value: '这是第2个' },
16 | { key: 3, value: '这是第3个' },
17 | ])
18 |
19 | useEffect(() => {
20 | console.log('[useEffect] Page Entry loaded!')
21 | return () => {
22 | console.log('[useEffect] Page Entry destroyed!')
23 | }
24 | }, [])
25 |
26 | // useReady Hook
27 | useReady(() => {
28 | console.log('[useReady] Page Entry ready')
29 | })
30 |
31 | // useDidShow
32 | useDidShow(() => {
33 | console.log('[useDidShow] Page Entry show')
34 | })
35 |
36 | // useDidHide
37 | useDidHide(() => {
38 | console.log('[useDidHide] Page Entry hide')
39 | })
40 |
41 | const increment = () => {
42 | setCount((val) => val + 1)
43 | }
44 |
45 | const changeName = (e: any) => {
46 | const value = e?.detail?.value || ''
47 | setName(value)
48 | }
49 |
50 | const addItem = () => {
51 | setList((arr) => arr.concat({ key: arr.length + 1, value: `这是第${arr.length + 1}个` }))
52 | }
53 |
54 | const deleteItem = () => {
55 | setList((arr) => arr.length <= 1 ? arr : arr.slice(0, -1))
56 | }
57 |
58 | const go = () => {
59 | native.navigateTo({
60 | url: '/pages/page-second/index',
61 | })
62 | }
63 |
64 | return (
65 |
66 |
67 | style样式
68 | css样式
69 |
70 | 列表
71 |
72 | {list.map((p) => {
73 | return {p.value}
74 | })}
75 |
76 |
77 |
80 |
83 |
84 |
85 | count: {count}
86 |
89 |
90 |
91 | name: {name}
92 | changeName(e)}
95 | />
96 |
97 |
98 |
101 |
102 |
103 | )
104 | }
105 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## mini-taro 实现一个react版本的Taro原型
2 | > 本项目以核心代码700行左右的代码量,实现了一套Taro运行时原型,可以帮助大家更好得理解Taro原理;
3 | >> 配套的技术文章:https://juejin.cn/post/7055597134279606286
4 |
5 | #### 能力说明
6 |
7 | 1. 基本运行时能力与Taro接近,支持将多个react语法构建的页面渲染到小程序上;
8 | 2. 支持react类组件,react函数组件,支持react hooks;支持使用css样式文件;
9 | 3. 实现了基本的事件系统、生命周期系统;支持useReady/useDidShow/useDidHide等hooks;
10 | 4. mini-taro侧重于运行时,构建层做简单实现;
11 |
12 | #### 文件夹层级说明
13 | ```
14 | /* 核心文件夹 */
15 | -dist // 产物文件夹
16 | -demo // demo页面React组件、应用配置文件夹
17 | -src // 本项目实现的Taro原型运行时核心代码
18 |
19 | /* 其他文件夹 */
20 | -build // 构建辅助文件夹(相当于loader)
21 | -scripts // 打包脚本/模版文件
22 |
23 | ```
24 | #### 体验
25 |
26 | 1. 安装依赖(npm install或yarn);
27 | 2. 执行yarn build构建,(会先删除dist文件夹)生成新的dist文件夹,然后使用小程序IDE预览dist文件即可;
28 | 3. 注意:本项目默认支持**头条小程序**,另外可支持**微信小程序**,编译微信小程序时要修改webpack.config.js如下配置:
29 | ```
30 | /**
31 | * 【小程序类型配置】
32 | * 对应小程序类型的配置,默认是字节小程序,另外支持微信小程序,需修改配置:
33 | * const globalObject = 'wx'
34 | * const fileType = {
35 | * templ: '.wxml',
36 | * style: '.wxss',
37 | * }
38 | */
39 |
40 | const globalObject = 'tt';
41 | const fileType = {
42 | templ: '.ttml',
43 | style: '.ttss',
44 | }
45 |
46 | ```
47 | 4. 关于windows下删除dist文件夹失败的情况,尝试全局安装rimraf: `npm install rimraf -g`;
48 | 5. 如果要支持头条小程序、微信小程序之外的小程序,参考[文档](./SUPPORT.md);
49 |
50 | #### 效果
51 |
52 | 1. 渲染出的小程序页面
53 |
54 |
55 |
56 |
57 |
58 | 2. 对应的React页面组件
59 |
60 | ```
61 | import React, { FC, useState } from 'react'
62 | import { View, Text, Button, Input } from '@/index'
63 |
64 | import './index.css'
65 |
66 | export const EntryPage: FC = () => {
67 |
68 | const [name, setName] = useState('')
69 | const [count, setCount] = useState(0)
70 | const [list, setList] = useState([
71 | { key: 1, value: '这是第1个' },
72 | { key: 2, value: '这是第2个' },
73 | { key: 3, value: '这是第3个' },
74 | ])
75 |
76 | const increment = () => {
77 | setCount((val) => val + 1)
78 | }
79 |
80 | const changeName = (e: any) => {
81 | const value = e?.detail?.value || ''
82 | setName(value)
83 | }
84 |
85 | // ... 省略
86 |
87 | return (
88 |
89 |
90 | style样式
91 | css样式
92 |
93 |
94 |
95 | count: {count}
96 |
99 |
100 |
101 | name: {name}
102 | changeName(e)}
105 | />
106 |
107 |
108 | )
109 | }
110 |
111 | ```
112 |
--------------------------------------------------------------------------------
/src/createPageConfig.ts:
--------------------------------------------------------------------------------
1 | import React, { ComponentClass, FunctionComponent } from 'react'
2 | import {
3 | getTaroElementById,
4 | getTaroRootElementByUid,
5 | PageContext,
6 | } from './createAppConfig'
7 | import { TaroRootElement } from './taro-element'
8 | import { isFunction } from './util'
9 |
10 | type PageComponent = FunctionComponent | ComponentClass
11 |
12 | export const createPageConfig = (
13 | Component: PageComponent,
14 | initData: Record,
15 | pageConfig: { path: string },
16 | ) => {
17 | const { path } = pageConfig
18 | const pageUid = path
19 |
20 | let app: any = null
21 | try {
22 | app = getApp()
23 | } catch (e) {
24 | console.error(e)
25 | }
26 |
27 | const getPageElement = () => {
28 | const rootElement = (app as any).getTree()
29 | return getTaroRootElementByUid(rootElement, pageUid) as TaroRootElement
30 | }
31 |
32 | const getElement = (id: string) => {
33 | const rootElement = (app as any).getTree()
34 | return getTaroElementById(rootElement, id)
35 | }
36 |
37 | // 所有事件汇总到一个方法上
38 | const eventHandler = (e: any) => {
39 | // 这里使用currentTarget是为了避免被冒泡影响
40 | const { type: eventName, currentTarget = {} } = e || {}
41 | const { id = '' } = currentTarget
42 | const pageElement = getPageElement()
43 | if (id && pageElement?.ctx) {
44 | const currentElement = getElement(id)
45 | if (!currentElement) return
46 | currentElement.dispatchEvent(eventName, e)
47 | }
48 | }
49 |
50 | const createConfig = () => {
51 | const config = Object.create({
52 | data: initData,
53 | onLoad: function (options: unknown) {
54 | console.warn('page onLoad', options)
55 | // 小程序page实例
56 | const page = this
57 | this.$taroPath = pageUid
58 | app &&
59 | app.mount(Component, this.$taroPath, () => {
60 | const pageElement = getPageElement()
61 | if (pageElement) {
62 | pageElement.ctx = page
63 | pageElement.performUpdate()
64 | }
65 | })
66 | },
67 | onShow: function() {
68 | safeExecute('onShow', pageUid)
69 | },
70 | onHide: function() {
71 | safeExecute('onHide', pageUid)
72 | },
73 | onReady: function() {
74 | safeExecute('onReady', pageUid)
75 | },
76 | onUnload: function () {
77 | app &&
78 | app.unmount(pageUid, () => {
79 | console.warn(`page: ${pageUid} unmount`)
80 | })
81 | },
82 | eh: eventHandler,
83 | })
84 |
85 | return config
86 | }
87 |
88 | return createConfig()
89 | }
90 |
91 | const hooks = new Map>()
92 |
93 | function safeExecute(lifeCycle: string, uid: string) {
94 | // 这里可以将小程序生命周期hook执行
95 | if (!hooks.has(uid)) return
96 | const target = hooks.get(uid)!
97 | const cb = target.get(lifeCycle)
98 | if (cb && isFunction(cb)) {
99 | cb.call(null)
100 | }
101 | }
102 |
103 | export function taroHook(lifeCycle: string) {
104 | return (cb: Function) => {
105 | // 这样拿到对应页面的uid
106 | const id = React.useContext(PageContext)
107 | const cbRef = React.useRef(cb)
108 |
109 | React.useLayoutEffect(() => {
110 | if (!isFunction(cbRef.current)) return
111 | if (!hooks.has(id)) hooks.set(id, new Map())
112 | const map = hooks.get(id)!
113 | map.set(lifeCycle, cbRef.current)
114 | }, [])
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const MiniCssExtractPlugin = require("mini-css-extract-plugin");
3 | const CopyPlugin = require("copy-webpack-plugin");
4 |
5 | /**
6 | * 【小程序类型配置】
7 | * 对应小程序类型的配置,默认是字节小程序,另外支持微信小程序,需修改配置:
8 | * const globalObject = 'wx'
9 | * const fileType = {
10 | * templ: '.wxml',
11 | * style: '.wxss',
12 | * }
13 | * 目前只支持字节小程序和微信小程序,其他小程序需参考说明增加模版文件并修改配置。
14 | */
15 |
16 | const globalObject = 'tt';
17 | const fileType = {
18 | templ: '.ttml',
19 | style: '.ttss',
20 | }
21 |
22 | module.exports = {
23 | mode: 'development',
24 | // 多入口,多路径多文件输出的方式
25 | entry: {
26 | 'app': './build/app.ts',
27 | 'pages/page-entry/index': './build/page-entry.ts',
28 | 'pages/page-second/index': './build/page-second.ts',
29 | },
30 | module: {
31 | rules: [
32 | {
33 | test: /\.(tsx|ts)$/,
34 | use: 'ts-loader',
35 | exclude: /node_modules/,
36 | },
37 | {
38 | test: /\.(css)$/,
39 | use: [
40 | MiniCssExtractPlugin.loader,
41 | "css-loader"
42 | ],
43 | },
44 | ]
45 | },
46 | output: {
47 | path: path.resolve(__dirname, 'dist'),
48 | filename: '[name].js',
49 | // 对应小程序的全局对象,比如微信小程序是'wx'
50 | globalObject,
51 | asyncChunks: false,
52 | },
53 | // 通过这个配置将公共依赖抽出,做为dist目录下的vendors.js依赖文件
54 | optimization: {
55 | splitChunks: {
56 | cacheGroups: {
57 | commons: {
58 | test: /[\\/]node_modules[\\/]/,
59 | name: "vendors",
60 | chunks: "all"
61 | }
62 | }
63 | },
64 | // 生成runtime可以保证每个页面和app.js都共享一个运行时,不会出现多个react实例
65 | runtimeChunk: {
66 | name: 'runtime',
67 | },
68 | },
69 | plugins: [
70 | new MiniCssExtractPlugin({
71 | filename: `[name]${fileType.style}`,
72 | }),
73 | // 模拟Taro构建出小程序文件的过程,并将taro生成的小程序模版直接拿来用.
74 | new CopyPlugin({
75 | patterns: [
76 | globalObject === 'tt' && {
77 | from: path.resolve(__dirname, './scripts/template/base.ttml'),
78 | to: path.resolve(__dirname, `./dist/base.ttml`),
79 | },
80 | globalObject === 'wx' && {
81 | from: path.resolve(__dirname, './scripts/template/base.wxml'),
82 | to: path.resolve(__dirname, `./dist/base.wxml`),
83 | },
84 | globalObject === 'wx' && {
85 | from: path.resolve(__dirname, './scripts/template/utils.wxs'),
86 | to: path.resolve(__dirname, `./dist/utils.wxs`),
87 | },
88 | {
89 | // page.ttml模版需要替换下引用的文件名
90 | from: path.resolve(__dirname, './scripts/template/page.ttml'),
91 | transform: (content) => {
92 | return content.toString().replace('%base%', `base${fileType.templ}`)
93 | },
94 | to: path.resolve(__dirname, `./dist/pages/page-entry/index${fileType.templ}`),
95 | },
96 | {
97 | from: path.resolve(__dirname, './scripts/template/page.ttml'),
98 | transform: (content) => {
99 | return content.toString().replace('%base%', `base${fileType.templ}`)
100 | },
101 | to: path.resolve(__dirname, `./dist/pages/page-second/index${fileType.templ}`),
102 | },
103 | {
104 | from: path.resolve(__dirname, './demo/project.config.json'),
105 | to: path.resolve(__dirname, './dist/project.config.json'),
106 | },
107 | {
108 | from: path.resolve(__dirname, './demo/app.config.json'),
109 | to: path.resolve(__dirname, './dist/app.json'),
110 | }
111 | ].filter(Boolean)
112 | })
113 | ],
114 | resolve: {
115 | extensions: [
116 | '.tsx',
117 | '.ts',
118 | '.js',
119 | '.css',
120 | ],
121 | alias: {
122 | '@/index': path.resolve(__dirname, 'src/index')
123 | }
124 | },
125 | devtool: "source-map"
126 | };
127 |
--------------------------------------------------------------------------------
/dist/runtime.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"runtime.js","mappings":";;;;UAAA;UACA;;UAEA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;;UAEA;UACA;;UAEA;UACA;;UAEA;UACA;UACA;;UAEA;UACA;;;;;WC5BA;WACA;WACA;WACA;WACA,+BAA+B,wCAAwC;WACvE;WACA;WACA;WACA;WACA,iBAAiB,qBAAqB;WACtC;WACA;WACA,kBAAkB,qBAAqB;WACvC;WACA;WACA,KAAK;WACL;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;;;;;WC3BA;;;;;WCAA;WACA;WACA;WACA,uDAAuD,iBAAiB;WACxE;WACA,gDAAgD,aAAa;WAC7D;;;;;WCNA;WACA;WACA;WACA;WACA;;;;;WCJA;;WAEA;WACA;WACA;WACA;WACA;WACA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA,MAAM,qBAAqB;WAC3B;WACA;WACA;WACA;WACA;WACA;WACA;WACA;;WAEA;WACA;WACA","sources":["webpack://mini-taro/webpack/bootstrap","webpack://mini-taro/webpack/runtime/chunk loaded","webpack://mini-taro/webpack/runtime/hasOwnProperty shorthand","webpack://mini-taro/webpack/runtime/make namespace object","webpack://mini-taro/webpack/runtime/node module decorator","webpack://mini-taro/webpack/runtime/jsonp chunk loading","webpack://mini-taro/webpack/before-startup","webpack://mini-taro/webpack/startup","webpack://mini-taro/webpack/after-startup"],"sourcesContent":["// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\tid: moduleId,\n\t\tloaded: false,\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n\t// Flag the module as loaded\n\tmodule.loaded = true;\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n// expose the modules object (__webpack_modules__)\n__webpack_require__.m = __webpack_modules__;\n\n","var deferred = [];\n__webpack_require__.O = (result, chunkIds, fn, priority) => {\n\tif(chunkIds) {\n\t\tpriority = priority || 0;\n\t\tfor(var i = deferred.length; i > 0 && deferred[i - 1][2] > priority; i--) deferred[i] = deferred[i - 1];\n\t\tdeferred[i] = [chunkIds, fn, priority];\n\t\treturn;\n\t}\n\tvar notFulfilled = Infinity;\n\tfor (var i = 0; i < deferred.length; i++) {\n\t\tvar [chunkIds, fn, priority] = deferred[i];\n\t\tvar fulfilled = true;\n\t\tfor (var j = 0; j < chunkIds.length; j++) {\n\t\t\tif ((priority & 1 === 0 || notFulfilled >= priority) && Object.keys(__webpack_require__.O).every((key) => (__webpack_require__.O[key](chunkIds[j])))) {\n\t\t\t\tchunkIds.splice(j--, 1);\n\t\t\t} else {\n\t\t\t\tfulfilled = false;\n\t\t\t\tif(priority < notFulfilled) notFulfilled = priority;\n\t\t\t}\n\t\t}\n\t\tif(fulfilled) {\n\t\t\tdeferred.splice(i--, 1)\n\t\t\tvar r = fn();\n\t\t\tif (r !== undefined) result = r;\n\t\t}\n\t}\n\treturn result;\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","__webpack_require__.nmd = (module) => {\n\tmodule.paths = [];\n\tif (!module.children) module.children = [];\n\treturn module;\n};","// no baseURI\n\n// object to store loaded and loading chunks\n// undefined = chunk not loaded, null = chunk preloaded/prefetched\n// [resolve, reject, Promise] = chunk loading, 0 = chunk loaded\nvar installedChunks = {\n\t\"runtime\": 0\n};\n\n// no chunk on demand loading\n\n// no prefetching\n\n// no preloaded\n\n// no HMR\n\n// no HMR manifest\n\n__webpack_require__.O.j = (chunkId) => (installedChunks[chunkId] === 0);\n\n// install a JSONP callback for chunk loading\nvar webpackJsonpCallback = (parentChunkLoadingFunction, data) => {\n\tvar [chunkIds, moreModules, runtime] = data;\n\t// add \"moreModules\" to the modules object,\n\t// then flag all \"chunkIds\" as loaded and fire callback\n\tvar moduleId, chunkId, i = 0;\n\tif(chunkIds.some((id) => (installedChunks[id] !== 0))) {\n\t\tfor(moduleId in moreModules) {\n\t\t\tif(__webpack_require__.o(moreModules, moduleId)) {\n\t\t\t\t__webpack_require__.m[moduleId] = moreModules[moduleId];\n\t\t\t}\n\t\t}\n\t\tif(runtime) var result = runtime(__webpack_require__);\n\t}\n\tif(parentChunkLoadingFunction) parentChunkLoadingFunction(data);\n\tfor(;i < chunkIds.length; i++) {\n\t\tchunkId = chunkIds[i];\n\t\tif(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) {\n\t\t\tinstalledChunks[chunkId][0]();\n\t\t}\n\t\tinstalledChunks[chunkId] = 0;\n\t}\n\treturn __webpack_require__.O(result);\n}\n\nvar chunkLoadingGlobal = tt[\"webpackChunkmini_taro\"] = tt[\"webpackChunkmini_taro\"] || [];\nchunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));\nchunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));","","",""],"names":[],"sourceRoot":""}
--------------------------------------------------------------------------------
/src/createAppConfig.ts:
--------------------------------------------------------------------------------
1 | import React, { ComponentClass } from 'react'
2 | import { NodeType, Props } from './interface'
3 | import { render } from './render'
4 | import { TaroElement, TaroRootElement } from './taro-element'
5 |
6 | export interface ReactPageComponent extends ComponentClass {}
7 |
8 | const RootElement = new TaroElement({
9 | id: Infinity,
10 | props: {},
11 | nodeName: 'view',
12 | type: NodeType.ROOT,
13 | })
14 |
15 | type PageElement = React.CElement>
16 |
17 | type PageComponent = () => PageElement
18 |
19 | const h = React.createElement
20 |
21 | const EMPTY_OBJ: any = {}
22 |
23 | export let PageContext: React.Context = EMPTY_OBJ
24 |
25 | const connectReactPage = (id: string) => {
26 | return (component: ReactPageComponent) => {
27 | if (PageContext === EMPTY_OBJ) {
28 | PageContext = React.createContext('')
29 | }
30 |
31 | return class Page extends React.Component {
32 | componentDidCatch(error: Error, info: React.ErrorInfo) {
33 | if (process.env.NODE_ENV !== 'production') {
34 | console.warn(error)
35 | console.error(info.componentStack)
36 | }
37 | }
38 |
39 | render() {
40 | // 名称是'root'的dom节点就是页面顶层节点了,在hostConfig中处理成不同节点
41 | return h(
42 | 'root',
43 | { pageId: id },
44 | h(
45 | PageContext.Provider,
46 | { value: id },
47 | h(component, { ...this.props }),
48 | ),
49 | )
50 | }
51 | }
52 | }
53 | }
54 |
55 | export const createAppConfig = (App: React.ComponentClass) => {
56 | // 这个Wrapper就是一个代理,将小程序的更新代理到这个页面组件上,再将页面最近的更新setData回小程序
57 | class AppWrapper extends React.Component {
58 | public pages: PageComponent[] = []
59 | public elements: PageElement[] = []
60 |
61 | public mount(component: ComponentClass, id: string, cb: () => void) {
62 | const key = id
63 | const page = () => h(component, { key, tid: id })
64 | this.pages.push(page)
65 |
66 | // 强制更新一次
67 | this.forceUpdate(cb)
68 | }
69 |
70 | public unmount(id: string, cb: () => void) {
71 | const idx = this.elements.findIndex((item) => item.props.tid === id)
72 | this.elements.splice(idx, 1)
73 |
74 | this.forceUpdate(cb)
75 | }
76 |
77 | public render() {
78 | while (this.pages.length > 0) {
79 | const page = this.pages.pop()!
80 | this.elements.push(page())
81 | }
82 |
83 | const props: Record | null = null
84 |
85 | return React.createElement(App, props, this.elements.slice())
86 | }
87 | }
88 |
89 | const wrapper: AppWrapper = render(
90 | React.createElement(AppWrapper),
91 | RootElement,
92 | ) as AppWrapper
93 |
94 | const createConfig = () => {
95 | // app的配置不能是一个复杂对象。。
96 | const config = Object.create({
97 | mount: function (
98 | component: ReactPageComponent,
99 | id: string,
100 | cb: () => void,
101 | ) {
102 | const page = connectReactPage(id)(component)
103 | wrapper.mount(page, id, cb)
104 | },
105 | unmount: function (id: string, cb: () => void) {
106 | wrapper.unmount(id, cb)
107 | },
108 | onLaunch: function (options: Record) {
109 | console.warn('app onLaunch')
110 | },
111 | onShow: function (options: any) {
112 | //
113 | },
114 | onHide: function () {
115 | //
116 | },
117 | onError: function (msg: any) {
118 | console.log('app error', msg)
119 | },
120 | getTree: function () {
121 | return RootElement
122 | },
123 | })
124 |
125 | return config
126 | }
127 |
128 | return createConfig()
129 | }
130 |
131 | // 页面顶层元素是TaroRootElement props内包含一个pageId,值是当前页面路径
132 | // 查找指定的页面root元素
133 | export function getTaroRootElementByUid(
134 | rootElement: TaroElement,
135 | uid: string,
136 | ): TaroRootElement | undefined {
137 | const queue: TaroElement[] = []
138 | let target: TaroRootElement | undefined = undefined
139 | // 广度优先查找
140 | queue.push(rootElement)
141 | while (queue.length > 0) {
142 | const t = queue.shift()!
143 | if (t.getAttribute('pageId') === uid) {
144 | target = t as TaroRootElement
145 | break
146 | } else {
147 | t.children?.map((item) => queue.push(item))
148 | }
149 | }
150 | return target
151 | }
152 |
153 | // 查找指定的页面内元素
154 | export function getTaroElementById(
155 | rootElement: TaroElement,
156 | id: string,
157 | ): TaroElement | undefined {
158 | const queue: TaroElement[] = []
159 | let target: TaroElement | undefined = undefined
160 | queue.push(rootElement)
161 | while (queue.length > 0) {
162 | const t = queue.shift()!
163 | if (t.getAttribute('uid') === id) {
164 | target = t as TaroElement
165 | break
166 | } else {
167 | t.children?.map((item) => queue.push(item))
168 | }
169 | }
170 | return target
171 | }
172 |
--------------------------------------------------------------------------------
/src/host-config.ts:
--------------------------------------------------------------------------------
1 | // 这边要借助 react-reconciler 实现一套虚拟dom树的系统
2 |
3 | import ReactReconciler, { HostConfig } from 'react-reconciler'
4 | import { NodeName, NodeType, Props } from './interface'
5 | import { generate, NodeTypeMap, noop } from './util'
6 | import { TaroElement, TaroRootElement, TaroText } from './taro-element'
7 |
8 | export function updateProps (dom: TaroElement, oldProps: Props, newProps: Props) {
9 | let i: string
10 |
11 | for (i in oldProps) {
12 | if (!(i in newProps)) {
13 | setProperty(dom, i, null, oldProps[i])
14 | }
15 | }
16 | for (i in newProps) {
17 | if (oldProps[i] !== newProps[i]) {
18 | setProperty(dom, i, newProps[i], oldProps[i])
19 | }
20 | }
21 | }
22 |
23 | function isEventName (s: string) {
24 | return s[0] === 'o' && s[1] === 'n'
25 | }
26 |
27 | function setEvent(dom: TaroElement, name: string, value: unknown) {
28 | let eventName = name.toLowerCase().slice(2)
29 |
30 | if (eventName === 'click') {
31 | eventName = 'tap'
32 | }
33 |
34 | if (typeof value === 'function') {
35 | dom.addEventListener(eventName, value)
36 | } else {
37 | dom.removeEventListener(eventName)
38 | }
39 | }
40 |
41 | /**
42 | * 判断属性,处理属性
43 | */
44 | function setProperty (dom: TaroElement, name: string, value: unknown, oldValue?: unknown) {
45 | // className转class
46 | name = name === 'className' ? 'class' : name
47 |
48 | if (
49 | name === 'key' ||
50 | name === 'children' ||
51 | name === 'ref'
52 | ) {
53 | // 跳过
54 | } else if (name === 'style') {
55 | // TODO 需要处理style的变化
56 | } else if (isEventName(name)) {
57 | // 事件 onClick等
58 | setEvent(dom, name, value)
59 | } else if (value == null) {
60 | dom.removeAttribute(name)
61 | } else {
62 | dom.setAttribute(name, value as string)
63 | }
64 | }
65 |
66 | const hostConfig: HostConfig<
67 | string, // Type
68 | Props, // Props
69 | TaroElement, // Container
70 | TaroElement, // Instance
71 | TaroText, // TextInstance
72 | TaroElement, // SuspenseInstance
73 | TaroElement, // HydratableInstance
74 | TaroElement, // PublicInstance
75 | Record, // HostContext
76 | string[], // UpdatePayload
77 | unknown, // ChildSet
78 | unknown, // TimeoutHandle
79 | unknown // NoTimeout
80 | > = {
81 | createInstance(
82 | type: string,
83 | props: Props,
84 | rootContainer: TaroElement,
85 | hostContext: unknown,
86 | internalHandle: unknown,
87 | ): TaroElement {
88 | const conf = {
89 | id: generate(),
90 | type: NodeTypeMap[type] || NodeType.VIEW,
91 | nodeName: type as NodeName,
92 | props,
93 | children: [],
94 | }
95 | if (type === 'root') {
96 | return new TaroRootElement(conf)
97 | } else {
98 | return new TaroElement(conf)
99 | }
100 | },
101 | createTextInstance(
102 | text: string,
103 | rootContainer: TaroText,
104 | hostContext: unknown,
105 | internalHandle: unknown,
106 | ): TaroText {
107 | const vnode = new TaroText({
108 | id: generate(),
109 | type: NodeType.TEXT,
110 | nodeName: 'text',
111 | props: {},
112 | text,
113 | })
114 | return vnode
115 | },
116 | getPublicInstance (inst: TaroElement) {
117 | return inst
118 | },
119 |
120 | getRootHostContext () {
121 | return {}
122 | },
123 |
124 | getChildHostContext () {
125 | return {}
126 | },
127 |
128 | appendChild (parent, child) {
129 | parent.appendChild(child)
130 | },
131 |
132 | appendInitialChild (parent, child) {
133 | parent.appendChild(child)
134 | },
135 |
136 | appendChildToContainer (parent, child) {
137 | parent.appendChild(child)
138 | },
139 |
140 | removeChild (parent, child) {
141 | parent.removeChild(child)
142 | },
143 |
144 | removeChildFromContainer (parent, child) {
145 | parent.removeChild(child)
146 | },
147 |
148 | insertBefore (parent, child, refChild) {
149 | parent.insertBefore(child, refChild)
150 | },
151 |
152 | insertInContainerBefore (parent, child, refChild) {
153 | parent.insertBefore(child, refChild)
154 | },
155 |
156 | commitTextUpdate (textInst, _, newText) {
157 | textInst.textContext = newText
158 | },
159 |
160 | finalizeInitialChildren (dom, _, props) {
161 | updateProps(dom, {}, props)
162 | return false
163 | },
164 |
165 | prepareUpdate () {
166 | return []
167 | },
168 |
169 | commitUpdate (dom, _payload, _type, oldProps, newProps) {
170 | updateProps(dom, oldProps, newProps)
171 | },
172 |
173 | clearContainer (element) {
174 | if (element.children && element.children.length > 0) {
175 | element.children = []
176 | }
177 | },
178 |
179 | shouldSetTextContent: () => false,
180 | prepareForCommit (..._: any[]) { return null },
181 | resetAfterCommit: noop,
182 | commitMount: noop,
183 | now: () => Date.now(),
184 | cancelTimeout: clearTimeout,
185 | scheduleTimeout: setTimeout,
186 | preparePortalMount: noop,
187 | noTimeout: -1,
188 | supportsMutation: true,
189 | supportsPersistence: false,
190 | isPrimaryRenderer: true,
191 | supportsHydration: false
192 | }
193 |
194 | const TaroReconciler = ReactReconciler(hostConfig)
195 |
196 | // TaroReconciler.injectIntoDevTools({
197 | // bundleType: 1,
198 | // version: '0.0.1',
199 | // rendererPackageName: 'mini-taro'
200 | // })
201 |
202 | export {
203 | TaroReconciler,
204 | ReactReconciler,
205 | }
206 |
--------------------------------------------------------------------------------
/src/taro-element.ts:
--------------------------------------------------------------------------------
1 | import { MpInstance, NodeName, NodeType, Props, UpdatePayload } from "./interface";
2 | import { RootName, Short } from "./short-cut";
3 | import { generateUid, hydrate } from "./hydrate";
4 |
5 | export class TaroElement {
6 |
7 | public props: Props
8 | public _rootContainer: unknown
9 |
10 | public id: number;
11 | public type: NodeType;
12 | public nodeName: NodeName;
13 | public children?: TaroElement[];
14 | public text?: string;
15 | public parentNode: TaroElement | null = null
16 | public __handlers: Map = new Map()
17 |
18 | constructor(params: {
19 | id: number;
20 | type: NodeType;
21 | nodeName: NodeName;
22 | props: Props
23 | children?: TaroElement[];
24 | text?: string;
25 | }) {
26 | const { id, type, nodeName, props, children, text } = params
27 | this.id = id
28 | this.type = type
29 | this.nodeName = nodeName
30 | this.props = Object.assign({ uid: generateUid(id) }, props)
31 | // props中的children是冗余数据
32 | if (this.props['children']) {
33 | delete this.props['children']
34 | }
35 | this.children = children
36 | this.text = text
37 | }
38 |
39 | private onAttributeUpdate() {
40 | if (!this.parentNode) return
41 | const index = this.parentNode.children?.findIndex((p) => p.id === this.id)
42 | if (!index || index < 0) return
43 | this._root?.enqueueUpdate({
44 | path: `${this.parentNode?._path}.${Short.Childnodes}[${index}]`,
45 | value: hydrate(this),
46 | })
47 | }
48 |
49 | setAttribute(name: string, value: unknown) {
50 | this.props[name] = value
51 | this.onAttributeUpdate()
52 | }
53 |
54 | getAttribute(name: string) {
55 | return this.props[name]
56 | }
57 |
58 | removeAttribute(name: string) {
59 | if (name in this.props) {
60 | delete this.props[name]
61 | this.onAttributeUpdate()
62 | }
63 | }
64 |
65 | get _path (): string {
66 | const parentNode = this.parentNode
67 | if (!parentNode || !Array.isArray(parentNode.children)) return ''
68 | const index = parentNode.children.findIndex((p) => p.id === this.id)
69 | return `${parentNode._path}.${Short.Childnodes}[${index}]`
70 | }
71 |
72 | protected get _root (): TaroRootElement | null {
73 | return this.parentNode?._root || null
74 | }
75 |
76 | appendChild(child: TaroElement) {
77 | child.parentNode = this
78 | if (!this.children) {
79 | this.children = [child]
80 | } else {
81 | this.children.push(child)
82 | }
83 | this._root?.enqueueUpdate({
84 | path: `${this._path}.${Short.Childnodes}`,
85 | value: this.children.map(hydrate),
86 | })
87 | }
88 |
89 | insertBefore(child: TaroElement, beforeChild: TaroElement) {
90 | if (!this.children) return
91 | const index = this.children.findIndex((item) => item.id === beforeChild.id)
92 | if (index < 0) return
93 | child.parentNode = this
94 | this.children.splice(index, 0, child)
95 | this._root?.enqueueUpdate({
96 | path: `${this._path}.${Short.Childnodes}`,
97 | value: this.children.map(hydrate),
98 | })
99 | }
100 |
101 | removeChild(child: TaroElement) {
102 | if (!this.children) return
103 | const index = this.children.findIndex((item) => item.id === child.id)
104 | if (index < 0) return
105 | this.children.splice(index, 1)
106 | this._root?.enqueueUpdate({
107 | path: `${this._path}.${Short.Childnodes}`,
108 | value: this.children.map(hydrate),
109 | })
110 | }
111 |
112 | addEventListener(eventName: string, value: Function) {
113 | this.__handlers.set(eventName, value)
114 | }
115 |
116 | removeEventListener(eventName: string) {
117 | this.__handlers.delete(eventName)
118 | }
119 |
120 | // 触发事件(忽略冒泡和捕获)
121 | // taro3内部会二次封装这个事件,处理冒泡等行为,此处简化
122 | dispatchEvent(eventName: string, e: unknown) {
123 | if (this.__handlers.has(eventName)) {
124 | const fn = this.__handlers.get(eventName)
125 | typeof fn === 'function' && fn(e)
126 | }
127 | }
128 | }
129 |
130 | export class TaroText extends TaroElement {
131 |
132 | constructor(params: {
133 | id: number
134 | type: NodeType
135 | nodeName: NodeName
136 | props: Props
137 | children?: TaroElement[]
138 | text?: string
139 | }) {
140 | super(params)
141 | this.nodeName = '#text'
142 | }
143 |
144 | set textContext(text: string) {
145 | this.text = text
146 | this._root?.enqueueUpdate({
147 | path: `${this._path}.${Short.Text}`,
148 | value: text,
149 | })
150 | }
151 | }
152 |
153 | export class TaroRootElement extends TaroElement {
154 | public updatePayloads: UpdatePayload[] = []
155 | public ctx: null | MpInstance = null
156 | private pendingUpdate: boolean = false
157 |
158 | constructor(params: {
159 | id: number
160 | type: NodeType
161 | nodeName: NodeName
162 | props: Props
163 | children?: TaroElement[]
164 | text?: string
165 | }) {
166 | super(params)
167 | }
168 |
169 | get _root(): TaroRootElement {
170 | return this
171 | }
172 |
173 | // 页面顶层root节点让_path返回顶层名称
174 | get _path (): string {
175 | return RootName
176 | }
177 |
178 | public enqueueUpdate (payload: UpdatePayload) {
179 | this.updatePayloads.push(payload)
180 |
181 | console.warn('updatePayloads', [...this.updatePayloads])
182 |
183 | if (!this.pendingUpdate && this.ctx !== null) {
184 | this.performUpdate()
185 | }
186 | }
187 |
188 | public performUpdate() {
189 | this.pendingUpdate = true
190 |
191 | // TODO 这里可以优化,将所有的复杂payloads合并为最小payloads,传给setData
192 | const elements: UpdatePayload[] = []
193 | while(this.updatePayloads.length > 0) {
194 | const item = this.updatePayloads.shift()!
195 | elements.push(item)
196 | }
197 |
198 | console.warn('setData before', elements, this.ctx)
199 |
200 | Promise.resolve().then(() => {
201 | while(elements.length > 0) {
202 | const item = elements.shift()!
203 | const { path, value } = item
204 | this.ctx?.setData({ [path]: value }, () => {
205 | console.warn('setData end')
206 | })
207 | }
208 | this.pendingUpdate = false
209 | })
210 | }
211 | }
212 |
--------------------------------------------------------------------------------
/dist/runtime.js:
--------------------------------------------------------------------------------
1 | /******/ (() => { // webpackBootstrap
2 | /******/ "use strict";
3 | /******/ var __webpack_modules__ = ({});
4 | /************************************************************************/
5 | /******/ // The module cache
6 | /******/ var __webpack_module_cache__ = {};
7 | /******/
8 | /******/ // The require function
9 | /******/ function __webpack_require__(moduleId) {
10 | /******/ // Check if module is in cache
11 | /******/ var cachedModule = __webpack_module_cache__[moduleId];
12 | /******/ if (cachedModule !== undefined) {
13 | /******/ return cachedModule.exports;
14 | /******/ }
15 | /******/ // Create a new module (and put it into the cache)
16 | /******/ var module = __webpack_module_cache__[moduleId] = {
17 | /******/ id: moduleId,
18 | /******/ loaded: false,
19 | /******/ exports: {}
20 | /******/ };
21 | /******/
22 | /******/ // Execute the module function
23 | /******/ __webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);
24 | /******/
25 | /******/ // Flag the module as loaded
26 | /******/ module.loaded = true;
27 | /******/
28 | /******/ // Return the exports of the module
29 | /******/ return module.exports;
30 | /******/ }
31 | /******/
32 | /******/ // expose the modules object (__webpack_modules__)
33 | /******/ __webpack_require__.m = __webpack_modules__;
34 | /******/
35 | /************************************************************************/
36 | /******/ /* webpack/runtime/chunk loaded */
37 | /******/ (() => {
38 | /******/ var deferred = [];
39 | /******/ __webpack_require__.O = (result, chunkIds, fn, priority) => {
40 | /******/ if(chunkIds) {
41 | /******/ priority = priority || 0;
42 | /******/ for(var i = deferred.length; i > 0 && deferred[i - 1][2] > priority; i--) deferred[i] = deferred[i - 1];
43 | /******/ deferred[i] = [chunkIds, fn, priority];
44 | /******/ return;
45 | /******/ }
46 | /******/ var notFulfilled = Infinity;
47 | /******/ for (var i = 0; i < deferred.length; i++) {
48 | /******/ var [chunkIds, fn, priority] = deferred[i];
49 | /******/ var fulfilled = true;
50 | /******/ for (var j = 0; j < chunkIds.length; j++) {
51 | /******/ if ((priority & 1 === 0 || notFulfilled >= priority) && Object.keys(__webpack_require__.O).every((key) => (__webpack_require__.O[key](chunkIds[j])))) {
52 | /******/ chunkIds.splice(j--, 1);
53 | /******/ } else {
54 | /******/ fulfilled = false;
55 | /******/ if(priority < notFulfilled) notFulfilled = priority;
56 | /******/ }
57 | /******/ }
58 | /******/ if(fulfilled) {
59 | /******/ deferred.splice(i--, 1)
60 | /******/ var r = fn();
61 | /******/ if (r !== undefined) result = r;
62 | /******/ }
63 | /******/ }
64 | /******/ return result;
65 | /******/ };
66 | /******/ })();
67 | /******/
68 | /******/ /* webpack/runtime/hasOwnProperty shorthand */
69 | /******/ (() => {
70 | /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
71 | /******/ })();
72 | /******/
73 | /******/ /* webpack/runtime/make namespace object */
74 | /******/ (() => {
75 | /******/ // define __esModule on exports
76 | /******/ __webpack_require__.r = (exports) => {
77 | /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
78 | /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
79 | /******/ }
80 | /******/ Object.defineProperty(exports, '__esModule', { value: true });
81 | /******/ };
82 | /******/ })();
83 | /******/
84 | /******/ /* webpack/runtime/node module decorator */
85 | /******/ (() => {
86 | /******/ __webpack_require__.nmd = (module) => {
87 | /******/ module.paths = [];
88 | /******/ if (!module.children) module.children = [];
89 | /******/ return module;
90 | /******/ };
91 | /******/ })();
92 | /******/
93 | /******/ /* webpack/runtime/jsonp chunk loading */
94 | /******/ (() => {
95 | /******/ // no baseURI
96 | /******/
97 | /******/ // object to store loaded and loading chunks
98 | /******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched
99 | /******/ // [resolve, reject, Promise] = chunk loading, 0 = chunk loaded
100 | /******/ var installedChunks = {
101 | /******/ "runtime": 0
102 | /******/ };
103 | /******/
104 | /******/ // no chunk on demand loading
105 | /******/
106 | /******/ // no prefetching
107 | /******/
108 | /******/ // no preloaded
109 | /******/
110 | /******/ // no HMR
111 | /******/
112 | /******/ // no HMR manifest
113 | /******/
114 | /******/ __webpack_require__.O.j = (chunkId) => (installedChunks[chunkId] === 0);
115 | /******/
116 | /******/ // install a JSONP callback for chunk loading
117 | /******/ var webpackJsonpCallback = (parentChunkLoadingFunction, data) => {
118 | /******/ var [chunkIds, moreModules, runtime] = data;
119 | /******/ // add "moreModules" to the modules object,
120 | /******/ // then flag all "chunkIds" as loaded and fire callback
121 | /******/ var moduleId, chunkId, i = 0;
122 | /******/ if(chunkIds.some((id) => (installedChunks[id] !== 0))) {
123 | /******/ for(moduleId in moreModules) {
124 | /******/ if(__webpack_require__.o(moreModules, moduleId)) {
125 | /******/ __webpack_require__.m[moduleId] = moreModules[moduleId];
126 | /******/ }
127 | /******/ }
128 | /******/ if(runtime) var result = runtime(__webpack_require__);
129 | /******/ }
130 | /******/ if(parentChunkLoadingFunction) parentChunkLoadingFunction(data);
131 | /******/ for(;i < chunkIds.length; i++) {
132 | /******/ chunkId = chunkIds[i];
133 | /******/ if(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) {
134 | /******/ installedChunks[chunkId][0]();
135 | /******/ }
136 | /******/ installedChunks[chunkId] = 0;
137 | /******/ }
138 | /******/ return __webpack_require__.O(result);
139 | /******/ }
140 | /******/
141 | /******/ var chunkLoadingGlobal = tt["webpackChunkmini_taro"] = tt["webpackChunkmini_taro"] || [];
142 | /******/ chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));
143 | /******/ chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));
144 | /******/ })();
145 | /******/
146 | /************************************************************************/
147 | /******/
148 | /******/
149 | /******/ })()
150 | ;
151 | //# sourceMappingURL=runtime.js.map
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/dist/base.ttml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 | {{i.v}}
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
--------------------------------------------------------------------------------
/scripts/template/base.ttml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 | {{i.v}}
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
--------------------------------------------------------------------------------
/dist/app.js:
--------------------------------------------------------------------------------
1 | require('./vendors.js');
2 | require('./runtime.js');
3 |
4 | "use strict";
5 | (tt["webpackChunkmini_taro"] = tt["webpackChunkmini_taro"] || []).push([["app"],{
6 |
7 | /***/ "./build/app.ts":
8 | /*!**********************!*\
9 | !*** ./build/app.ts ***!
10 | \**********************/
11 | /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
12 |
13 |
14 | var __importDefault = (this && this.__importDefault) || function (mod) {
15 | return (mod && mod.__esModule) ? mod : { "default": mod };
16 | };
17 | Object.defineProperty(exports, "__esModule", ({ value: true }));
18 | const app_1 = __importDefault(__webpack_require__(/*! ../demo/app */ "./demo/app.ts"));
19 | const index_1 = __webpack_require__(/*! @/index */ "./src/index.ts");
20 | /**
21 | *
22 | * 这里在编译后的结果里面需要将
23 | * require('./vendors.js');
24 | * require('./runtime.js');
25 | * 放在顶层
26 | * 让app.js立即执行
27 | */
28 | App((0, index_1.createAppConfig)(app_1.default));
29 |
30 |
31 | /***/ }),
32 |
33 | /***/ "./demo/app.ts":
34 | /*!*********************!*\
35 | !*** ./demo/app.ts ***!
36 | \*********************/
37 | /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
38 |
39 |
40 | var __importDefault = (this && this.__importDefault) || function (mod) {
41 | return (mod && mod.__esModule) ? mod : { "default": mod };
42 | };
43 | Object.defineProperty(exports, "__esModule", ({ value: true }));
44 | const react_1 = __importDefault(__webpack_require__(/*! react */ "./node_modules/react/index.js"));
45 | class AppComp extends react_1.default.Component {
46 | componentDidMount() {
47 | // ...
48 | }
49 | onLaunch(options) {
50 | console.warn('App onlaunch', options);
51 | }
52 | componentDidShow() {
53 | // ...
54 | }
55 | componentDidHide() {
56 | // ...
57 | }
58 | componentDidCatchError() {
59 | // ...
60 | }
61 | render() {
62 | return react_1.default.createElement('view', {}, this.props.children);
63 | }
64 | }
65 | exports["default"] = AppComp;
66 |
67 |
68 | /***/ }),
69 |
70 | /***/ "./src/createAppConfig.ts":
71 | /*!********************************!*\
72 | !*** ./src/createAppConfig.ts ***!
73 | \********************************/
74 | /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
75 |
76 |
77 | var __importDefault = (this && this.__importDefault) || function (mod) {
78 | return (mod && mod.__esModule) ? mod : { "default": mod };
79 | };
80 | Object.defineProperty(exports, "__esModule", ({ value: true }));
81 | exports.getTaroElementById = exports.getTaroRootElementByUid = exports.createAppConfig = exports.PageContext = void 0;
82 | const react_1 = __importDefault(__webpack_require__(/*! react */ "./node_modules/react/index.js"));
83 | const interface_1 = __webpack_require__(/*! ./interface */ "./src/interface.ts");
84 | const render_1 = __webpack_require__(/*! ./render */ "./src/render.ts");
85 | const taro_element_1 = __webpack_require__(/*! ./taro-element */ "./src/taro-element.ts");
86 | const RootElement = new taro_element_1.TaroElement({
87 | id: Infinity,
88 | props: {},
89 | nodeName: 'view',
90 | type: interface_1.NodeType.ROOT,
91 | });
92 | const h = react_1.default.createElement;
93 | const EMPTY_OBJ = {};
94 | exports.PageContext = EMPTY_OBJ;
95 | const connectReactPage = (id) => {
96 | return (component) => {
97 | if (exports.PageContext === EMPTY_OBJ) {
98 | exports.PageContext = react_1.default.createContext('');
99 | }
100 | return class Page extends react_1.default.Component {
101 | componentDidCatch(error, info) {
102 | if (true) {
103 | console.warn(error);
104 | console.error(info.componentStack);
105 | }
106 | }
107 | render() {
108 | // 名称是'root'的dom节点就是页面顶层节点了,在hostConfig中处理成不同节点
109 | return h('root', { pageId: id }, h(exports.PageContext.Provider, { value: id }, h(component, Object.assign({}, this.props))));
110 | }
111 | };
112 | };
113 | };
114 | const createAppConfig = (App) => {
115 | // 这个Wrapper就是一个代理,将小程序的更新代理到这个页面组件上,再将页面最近的更新setData回小程序
116 | class AppWrapper extends react_1.default.Component {
117 | constructor() {
118 | super(...arguments);
119 | this.pages = [];
120 | this.elements = [];
121 | }
122 | mount(component, id, cb) {
123 | const key = id;
124 | const page = () => h(component, { key, tid: id });
125 | this.pages.push(page);
126 | // 强制更新一次
127 | this.forceUpdate(cb);
128 | }
129 | unmount(id, cb) {
130 | const idx = this.elements.findIndex((item) => item.props.tid === id);
131 | this.elements.splice(idx, 1);
132 | this.forceUpdate(cb);
133 | }
134 | render() {
135 | while (this.pages.length > 0) {
136 | const page = this.pages.pop();
137 | this.elements.push(page());
138 | }
139 | const props = null;
140 | return react_1.default.createElement(App, props, this.elements.slice());
141 | }
142 | }
143 | const wrapper = (0, render_1.render)(react_1.default.createElement(AppWrapper), RootElement);
144 | const createConfig = () => {
145 | // app的配置不能是一个复杂对象。。
146 | const config = Object.create({
147 | mount: function (component, id, cb) {
148 | const page = connectReactPage(id)(component);
149 | wrapper.mount(page, id, cb);
150 | },
151 | unmount: function (id, cb) {
152 | wrapper.unmount(id, cb);
153 | },
154 | onLaunch: function (options) {
155 | console.warn('app onLaunch');
156 | },
157 | onShow: function (options) {
158 | //
159 | },
160 | onHide: function () {
161 | //
162 | },
163 | onError: function (msg) {
164 | console.log('app error', msg);
165 | },
166 | getTree: function () {
167 | return RootElement;
168 | },
169 | });
170 | return config;
171 | };
172 | return createConfig();
173 | };
174 | exports.createAppConfig = createAppConfig;
175 | // 页面顶层元素是TaroRootElement props内包含一个pageId,值是当前页面路径
176 | // 查找指定的页面root元素
177 | function getTaroRootElementByUid(rootElement, uid) {
178 | var _a;
179 | const queue = [];
180 | let target = undefined;
181 | // 广度优先查找
182 | queue.push(rootElement);
183 | while (queue.length > 0) {
184 | const t = queue.shift();
185 | if (t.getAttribute('pageId') === uid) {
186 | target = t;
187 | break;
188 | }
189 | else {
190 | (_a = t.children) === null || _a === void 0 ? void 0 : _a.map((item) => queue.push(item));
191 | }
192 | }
193 | return target;
194 | }
195 | exports.getTaroRootElementByUid = getTaroRootElementByUid;
196 | // 查找指定的页面内元素
197 | function getTaroElementById(rootElement, id) {
198 | var _a;
199 | const queue = [];
200 | let target = undefined;
201 | queue.push(rootElement);
202 | while (queue.length > 0) {
203 | const t = queue.shift();
204 | if (t.getAttribute('uid') === id) {
205 | target = t;
206 | break;
207 | }
208 | else {
209 | (_a = t.children) === null || _a === void 0 ? void 0 : _a.map((item) => queue.push(item));
210 | }
211 | }
212 | return target;
213 | }
214 | exports.getTaroElementById = getTaroElementById;
215 |
216 |
217 | /***/ }),
218 |
219 | /***/ "./src/createPageConfig.ts":
220 | /*!*********************************!*\
221 | !*** ./src/createPageConfig.ts ***!
222 | \*********************************/
223 | /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
224 |
225 |
226 | var __importDefault = (this && this.__importDefault) || function (mod) {
227 | return (mod && mod.__esModule) ? mod : { "default": mod };
228 | };
229 | Object.defineProperty(exports, "__esModule", ({ value: true }));
230 | exports.taroHook = exports.createPageConfig = void 0;
231 | const react_1 = __importDefault(__webpack_require__(/*! react */ "./node_modules/react/index.js"));
232 | const createAppConfig_1 = __webpack_require__(/*! ./createAppConfig */ "./src/createAppConfig.ts");
233 | const util_1 = __webpack_require__(/*! ./util */ "./src/util.ts");
234 | const createPageConfig = (Component, initData, pageConfig) => {
235 | const { path } = pageConfig;
236 | const pageUid = path;
237 | let app = null;
238 | try {
239 | app = getApp();
240 | }
241 | catch (e) {
242 | console.error(e);
243 | }
244 | const getPageElement = () => {
245 | const rootElement = app.getTree();
246 | return (0, createAppConfig_1.getTaroRootElementByUid)(rootElement, pageUid);
247 | };
248 | const getElement = (id) => {
249 | const rootElement = app.getTree();
250 | return (0, createAppConfig_1.getTaroElementById)(rootElement, id);
251 | };
252 | // 所有事件汇总到一个方法上
253 | const eventHandler = (e) => {
254 | // 这里使用currentTarget是为了避免被冒泡影响
255 | const { type: eventName, currentTarget = {} } = e || {};
256 | const { id = '' } = currentTarget;
257 | const pageElement = getPageElement();
258 | if (id && (pageElement === null || pageElement === void 0 ? void 0 : pageElement.ctx)) {
259 | const currentElement = getElement(id);
260 | if (!currentElement)
261 | return;
262 | currentElement.dispatchEvent(eventName, e);
263 | }
264 | };
265 | const createConfig = () => {
266 | const config = Object.create({
267 | data: initData,
268 | onLoad: function (options) {
269 | console.warn('page onLoad', options);
270 | // 小程序page实例
271 | const page = this;
272 | this.$taroPath = pageUid;
273 | app &&
274 | app.mount(Component, this.$taroPath, () => {
275 | const pageElement = getPageElement();
276 | if (pageElement) {
277 | pageElement.ctx = page;
278 | pageElement.performUpdate();
279 | }
280 | });
281 | },
282 | onShow: function () {
283 | safeExecute('onShow', pageUid);
284 | },
285 | onHide: function () {
286 | safeExecute('onHide', pageUid);
287 | },
288 | onReady: function () {
289 | safeExecute('onReady', pageUid);
290 | },
291 | onUnload: function () {
292 | app &&
293 | app.unmount(pageUid, () => {
294 | console.warn(`page: ${pageUid} unmount`);
295 | });
296 | },
297 | eh: eventHandler,
298 | });
299 | return config;
300 | };
301 | return createConfig();
302 | };
303 | exports.createPageConfig = createPageConfig;
304 | const hooks = new Map();
305 | function safeExecute(lifeCycle, uid) {
306 | // 这里可以将小程序生命周期hook执行
307 | if (!hooks.has(uid))
308 | return;
309 | const target = hooks.get(uid);
310 | const cb = target.get(lifeCycle);
311 | if (cb && (0, util_1.isFunction)(cb)) {
312 | cb.call(null);
313 | }
314 | }
315 | function taroHook(lifeCycle) {
316 | return (cb) => {
317 | // 这样拿到对应页面的uid
318 | const id = react_1.default.useContext(createAppConfig_1.PageContext);
319 | const cbRef = react_1.default.useRef(cb);
320 | react_1.default.useLayoutEffect(() => {
321 | if (!(0, util_1.isFunction)(cbRef.current))
322 | return;
323 | if (!hooks.has(id))
324 | hooks.set(id, new Map());
325 | const map = hooks.get(id);
326 | map.set(lifeCycle, cbRef.current);
327 | }, []);
328 | };
329 | }
330 | exports.taroHook = taroHook;
331 |
332 |
333 | /***/ }),
334 |
335 | /***/ "./src/hooks.ts":
336 | /*!**********************!*\
337 | !*** ./src/hooks.ts ***!
338 | \**********************/
339 | /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
340 |
341 |
342 | Object.defineProperty(exports, "__esModule", ({ value: true }));
343 | exports.useDidHide = exports.useDidShow = exports.useReady = void 0;
344 | /**
345 | * 这里实现的hook对应Taro提供的hook
346 | * 参考这些hook可以实现出任意需要的业务hook,比如useShareAppMessage,usePageScroll等
347 | */
348 | const createPageConfig_1 = __webpack_require__(/*! ./createPageConfig */ "./src/createPageConfig.ts");
349 | exports.useReady = (0, createPageConfig_1.taroHook)('onReady');
350 | exports.useDidShow = (0, createPageConfig_1.taroHook)('onShow');
351 | exports.useDidHide = (0, createPageConfig_1.taroHook)('onHide');
352 |
353 |
354 | /***/ }),
355 |
356 | /***/ "./src/host-config.ts":
357 | /*!****************************!*\
358 | !*** ./src/host-config.ts ***!
359 | \****************************/
360 | /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
361 |
362 |
363 | // 这边要借助 react-reconciler 实现一套虚拟dom树的系统
364 | var __importDefault = (this && this.__importDefault) || function (mod) {
365 | return (mod && mod.__esModule) ? mod : { "default": mod };
366 | };
367 | Object.defineProperty(exports, "__esModule", ({ value: true }));
368 | exports.ReactReconciler = exports.TaroReconciler = exports.updateProps = void 0;
369 | const react_reconciler_1 = __importDefault(__webpack_require__(/*! react-reconciler */ "./node_modules/react-reconciler/index.js"));
370 | exports.ReactReconciler = react_reconciler_1.default;
371 | const interface_1 = __webpack_require__(/*! ./interface */ "./src/interface.ts");
372 | const util_1 = __webpack_require__(/*! ./util */ "./src/util.ts");
373 | const taro_element_1 = __webpack_require__(/*! ./taro-element */ "./src/taro-element.ts");
374 | function updateProps(dom, oldProps, newProps) {
375 | let i;
376 | for (i in oldProps) {
377 | if (!(i in newProps)) {
378 | setProperty(dom, i, null, oldProps[i]);
379 | }
380 | }
381 | for (i in newProps) {
382 | if (oldProps[i] !== newProps[i]) {
383 | setProperty(dom, i, newProps[i], oldProps[i]);
384 | }
385 | }
386 | }
387 | exports.updateProps = updateProps;
388 | function isEventName(s) {
389 | return s[0] === 'o' && s[1] === 'n';
390 | }
391 | function setEvent(dom, name, value) {
392 | let eventName = name.toLowerCase().slice(2);
393 | if (eventName === 'click') {
394 | eventName = 'tap';
395 | }
396 | if (typeof value === 'function') {
397 | dom.addEventListener(eventName, value);
398 | }
399 | else {
400 | dom.removeEventListener(eventName);
401 | }
402 | }
403 | /**
404 | * 判断属性,处理属性
405 | */
406 | function setProperty(dom, name, value, oldValue) {
407 | // className转class
408 | name = name === 'className' ? 'class' : name;
409 | if (name === 'key' ||
410 | name === 'children' ||
411 | name === 'ref') {
412 | // 跳过
413 | }
414 | else if (name === 'style') {
415 | // TODO 需要处理style的变化
416 | }
417 | else if (isEventName(name)) {
418 | // 事件 onClick等
419 | setEvent(dom, name, value);
420 | }
421 | else if (value == null) {
422 | dom.removeAttribute(name);
423 | }
424 | else {
425 | dom.setAttribute(name, value);
426 | }
427 | }
428 | const hostConfig = {
429 | createInstance(type, props, rootContainer, hostContext, internalHandle) {
430 | const conf = {
431 | id: (0, util_1.generate)(),
432 | type: util_1.NodeTypeMap[type] || interface_1.NodeType.VIEW,
433 | nodeName: type,
434 | props,
435 | children: [],
436 | };
437 | if (type === 'root') {
438 | return new taro_element_1.TaroRootElement(conf);
439 | }
440 | else {
441 | return new taro_element_1.TaroElement(conf);
442 | }
443 | },
444 | createTextInstance(text, rootContainer, hostContext, internalHandle) {
445 | const vnode = new taro_element_1.TaroText({
446 | id: (0, util_1.generate)(),
447 | type: interface_1.NodeType.TEXT,
448 | nodeName: 'text',
449 | props: {},
450 | text,
451 | });
452 | return vnode;
453 | },
454 | getPublicInstance(inst) {
455 | return inst;
456 | },
457 | getRootHostContext() {
458 | return {};
459 | },
460 | getChildHostContext() {
461 | return {};
462 | },
463 | appendChild(parent, child) {
464 | parent.appendChild(child);
465 | },
466 | appendInitialChild(parent, child) {
467 | parent.appendChild(child);
468 | },
469 | appendChildToContainer(parent, child) {
470 | parent.appendChild(child);
471 | },
472 | removeChild(parent, child) {
473 | parent.removeChild(child);
474 | },
475 | removeChildFromContainer(parent, child) {
476 | parent.removeChild(child);
477 | },
478 | insertBefore(parent, child, refChild) {
479 | parent.insertBefore(child, refChild);
480 | },
481 | insertInContainerBefore(parent, child, refChild) {
482 | parent.insertBefore(child, refChild);
483 | },
484 | commitTextUpdate(textInst, _, newText) {
485 | textInst.textContext = newText;
486 | },
487 | finalizeInitialChildren(dom, _, props) {
488 | updateProps(dom, {}, props);
489 | return false;
490 | },
491 | prepareUpdate() {
492 | return [];
493 | },
494 | commitUpdate(dom, _payload, _type, oldProps, newProps) {
495 | updateProps(dom, oldProps, newProps);
496 | },
497 | clearContainer(element) {
498 | if (element.children && element.children.length > 0) {
499 | element.children = [];
500 | }
501 | },
502 | shouldSetTextContent: () => false,
503 | prepareForCommit(..._) { return null; },
504 | resetAfterCommit: util_1.noop,
505 | commitMount: util_1.noop,
506 | now: () => Date.now(),
507 | cancelTimeout: clearTimeout,
508 | scheduleTimeout: setTimeout,
509 | preparePortalMount: util_1.noop,
510 | noTimeout: -1,
511 | supportsMutation: true,
512 | supportsPersistence: false,
513 | isPrimaryRenderer: true,
514 | supportsHydration: false
515 | };
516 | const TaroReconciler = (0, react_reconciler_1.default)(hostConfig);
517 | exports.TaroReconciler = TaroReconciler;
518 |
519 |
520 | /***/ }),
521 |
522 | /***/ "./src/hydrate.ts":
523 | /*!************************!*\
524 | !*** ./src/hydrate.ts ***!
525 | \************************/
526 | /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
527 |
528 |
529 | var __rest = (this && this.__rest) || function (s, e) {
530 | var t = {};
531 | for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
532 | t[p] = s[p];
533 | if (s != null && typeof Object.getOwnPropertySymbols === "function")
534 | for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
535 | if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
536 | t[p[i]] = s[p[i]];
537 | }
538 | return t;
539 | };
540 | Object.defineProperty(exports, "__esModule", ({ value: true }));
541 | exports.styleTransform = exports.hydrate = exports.generateUid = void 0;
542 | const interface_1 = __webpack_require__(/*! ./interface */ "./src/interface.ts");
543 | const isEmpty = (children) => {
544 | return !children || (Array.isArray(children) && children.length === 0);
545 | };
546 | const generateUid = (id) => `u-${id}`;
547 | exports.generateUid = generateUid;
548 | /**
549 | *
550 | * 这个函数是将虚拟dom树转为了渲染属性树
551 | * @param node
552 | * @returns
553 | */
554 | const hydrate = (node) => {
555 | if (node.type === interface_1.NodeType.TEXT && isEmpty(node.children)) {
556 | return {
557 | ["v" /* Text */]: node.text || '',
558 | ["nn" /* NodeName */]: node.nodeName,
559 | };
560 | }
561 | else {
562 | const { nodeName, props, id, children } = node;
563 | const _a = props || {}, { className, style, children: _children } = _a, nextProps = __rest(_a, ["className", "style", "children"]);
564 | // style字符串化 + 驼峰转横线
565 | let styleContent = '';
566 | if (!!style) {
567 | for (const [key, value] of Object.entries(style)) {
568 | styleContent += `${(0, exports.styleTransform)(key)}: ${value};`;
569 | }
570 | }
571 | return Object.assign({ ["cn" /* Childnodes */]: !!children && Array.isArray(children) ? children.map(exports.hydrate) : [], ["nn" /* NodeName */]: nodeName, ["cl" /* Class */]: className || '', ["st" /* Style */]: styleContent, uid: (0, exports.generateUid)(id) }, nextProps);
572 | }
573 | };
574 | exports.hydrate = hydrate;
575 | // fontSize -> font-size
576 | const styleTransform = (name) => {
577 | const list = name.split('');
578 | const index = list.findIndex(p => /[A-Z]/.test(p));
579 | if (index >= 0)
580 | list.splice(index, 1, '-' + list[index].toLowerCase());
581 | return list.join('');
582 | };
583 | exports.styleTransform = styleTransform;
584 |
585 |
586 | /***/ }),
587 |
588 | /***/ "./src/index.ts":
589 | /*!**********************!*\
590 | !*** ./src/index.ts ***!
591 | \**********************/
592 | /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
593 |
594 |
595 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
596 | if (k2 === undefined) k2 = k;
597 | Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
598 | }) : (function(o, m, k, k2) {
599 | if (k2 === undefined) k2 = k;
600 | o[k2] = m[k];
601 | }));
602 | var __exportStar = (this && this.__exportStar) || function(m, exports) {
603 | for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
604 | };
605 | Object.defineProperty(exports, "__esModule", ({ value: true }));
606 | exports.createAppConfig = exports.createPageConfig = void 0;
607 | var createPageConfig_1 = __webpack_require__(/*! ./createPageConfig */ "./src/createPageConfig.ts");
608 | Object.defineProperty(exports, "createPageConfig", ({ enumerable: true, get: function () { return createPageConfig_1.createPageConfig; } }));
609 | var createAppConfig_1 = __webpack_require__(/*! ./createAppConfig */ "./src/createAppConfig.ts");
610 | Object.defineProperty(exports, "createAppConfig", ({ enumerable: true, get: function () { return createAppConfig_1.createAppConfig; } }));
611 | __exportStar(__webpack_require__(/*! ./hooks */ "./src/hooks.ts"), exports);
612 | __exportStar(__webpack_require__(/*! ./native-components */ "./src/native-components.ts"), exports);
613 |
614 |
615 | /***/ }),
616 |
617 | /***/ "./src/interface.ts":
618 | /*!**************************!*\
619 | !*** ./src/interface.ts ***!
620 | \**************************/
621 | /***/ ((__unused_webpack_module, exports) => {
622 |
623 |
624 | // import { VNode } from "./vnode"
625 | Object.defineProperty(exports, "__esModule", ({ value: true }));
626 | exports.NodeType = void 0;
627 | var NodeType;
628 | (function (NodeType) {
629 | NodeType[NodeType["ROOT"] = 0] = "ROOT";
630 | NodeType[NodeType["TEXT"] = 1] = "TEXT";
631 | NodeType[NodeType["VIEW"] = 2] = "VIEW";
632 | })(NodeType || (NodeType = {}));
633 | exports.NodeType = NodeType;
634 |
635 |
636 | /***/ }),
637 |
638 | /***/ "./src/native-components.ts":
639 | /*!**********************************!*\
640 | !*** ./src/native-components.ts ***!
641 | \**********************************/
642 | /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
643 |
644 |
645 | // 基础组件实现
646 | // 原生组件实际就是react组件,名称上与小程序组件对齐
647 | var __rest = (this && this.__rest) || function (s, e) {
648 | var t = {};
649 | for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
650 | t[p] = s[p];
651 | if (s != null && typeof Object.getOwnPropertySymbols === "function")
652 | for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
653 | if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
654 | t[p[i]] = s[p[i]];
655 | }
656 | return t;
657 | };
658 | var __importDefault = (this && this.__importDefault) || function (mod) {
659 | return (mod && mod.__esModule) ? mod : { "default": mod };
660 | };
661 | Object.defineProperty(exports, "__esModule", ({ value: true }));
662 | exports.Input = exports.Button = exports.Text = exports.View = void 0;
663 | const react_1 = __importDefault(__webpack_require__(/*! react */ "./node_modules/react/index.js"));
664 | const createNativeComponent = (name, props) => {
665 | return (params) => {
666 | const _a = params || {}, { children } = _a, nextParams = __rest(_a, ["children"]);
667 | return react_1.default.createElement(name, Object.assign({}, nextParams), children);
668 | };
669 | };
670 | exports.View = createNativeComponent('view', {});
671 | exports.Text = createNativeComponent('text', {});
672 | exports.Button = createNativeComponent('button', {});
673 | exports.Input = createNativeComponent('input', {});
674 |
675 |
676 | /***/ }),
677 |
678 | /***/ "./src/render.ts":
679 | /*!***********************!*\
680 | !*** ./src/render.ts ***!
681 | \***********************/
682 | /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
683 |
684 |
685 | // 这边要借助 react-reconciler 实现一套虚拟dom树的系统
686 | Object.defineProperty(exports, "__esModule", ({ value: true }));
687 | exports.render = void 0;
688 | const host_config_1 = __webpack_require__(/*! ./host-config */ "./src/host-config.ts");
689 | const render = (component, container) => {
690 | if (!container._rootContainer) {
691 | container._rootContainer = host_config_1.TaroReconciler.createContainer(container, 0, false, null);
692 | }
693 | host_config_1.TaroReconciler.updateContainer(component, container._rootContainer, null);
694 | return host_config_1.TaroReconciler.getPublicRootInstance(container._rootContainer);
695 | };
696 | exports.render = render;
697 |
698 |
699 | /***/ }),
700 |
701 | /***/ "./src/short-cut.ts":
702 | /*!**************************!*\
703 | !*** ./src/short-cut.ts ***!
704 | \**************************/
705 | /***/ ((__unused_webpack_module, exports) => {
706 |
707 |
708 | Object.defineProperty(exports, "__esModule", ({ value: true }));
709 | exports.Short = exports.RootName = void 0;
710 | exports.RootName = 'root';
711 | // 这里使用Taro的缩写
712 | var Short;
713 | (function (Short) {
714 | Short["Container"] = "container";
715 | Short["Childnodes"] = "cn";
716 | Short["Text"] = "v";
717 | Short["NodeType"] = "nt";
718 | Short["NodeName"] = "nn";
719 | Short["Style"] = "st";
720 | Short["Class"] = "cl";
721 | Short["Src"] = "src";
722 | })(Short = exports.Short || (exports.Short = {}));
723 |
724 |
725 | /***/ }),
726 |
727 | /***/ "./src/taro-element.ts":
728 | /*!*****************************!*\
729 | !*** ./src/taro-element.ts ***!
730 | \*****************************/
731 | /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
732 |
733 |
734 | Object.defineProperty(exports, "__esModule", ({ value: true }));
735 | exports.TaroRootElement = exports.TaroText = exports.TaroElement = void 0;
736 | const short_cut_1 = __webpack_require__(/*! ./short-cut */ "./src/short-cut.ts");
737 | const hydrate_1 = __webpack_require__(/*! ./hydrate */ "./src/hydrate.ts");
738 | class TaroElement {
739 | constructor(params) {
740 | this.parentNode = null;
741 | this.__handlers = new Map();
742 | const { id, type, nodeName, props, children, text } = params;
743 | this.id = id;
744 | this.type = type;
745 | this.nodeName = nodeName;
746 | this.props = Object.assign({ uid: (0, hydrate_1.generateUid)(id) }, props);
747 | // props中的children是冗余数据
748 | if (this.props['children']) {
749 | delete this.props['children'];
750 | }
751 | this.children = children;
752 | this.text = text;
753 | }
754 | onAttributeUpdate() {
755 | var _a, _b, _c;
756 | if (!this.parentNode)
757 | return;
758 | const index = (_a = this.parentNode.children) === null || _a === void 0 ? void 0 : _a.findIndex((p) => p.id === this.id);
759 | if (!index || index < 0)
760 | return;
761 | (_b = this._root) === null || _b === void 0 ? void 0 : _b.enqueueUpdate({
762 | path: `${(_c = this.parentNode) === null || _c === void 0 ? void 0 : _c._path}.${"cn" /* Childnodes */}[${index}]`,
763 | value: (0, hydrate_1.hydrate)(this),
764 | });
765 | }
766 | setAttribute(name, value) {
767 | this.props[name] = value;
768 | this.onAttributeUpdate();
769 | }
770 | getAttribute(name) {
771 | return this.props[name];
772 | }
773 | removeAttribute(name) {
774 | if (name in this.props) {
775 | delete this.props[name];
776 | this.onAttributeUpdate();
777 | }
778 | }
779 | get _path() {
780 | const parentNode = this.parentNode;
781 | if (!parentNode || !Array.isArray(parentNode.children))
782 | return '';
783 | const index = parentNode.children.findIndex((p) => p.id === this.id);
784 | return `${parentNode._path}.${"cn" /* Childnodes */}[${index}]`;
785 | }
786 | get _root() {
787 | var _a;
788 | return ((_a = this.parentNode) === null || _a === void 0 ? void 0 : _a._root) || null;
789 | }
790 | appendChild(child) {
791 | var _a;
792 | child.parentNode = this;
793 | if (!this.children) {
794 | this.children = [child];
795 | }
796 | else {
797 | this.children.push(child);
798 | }
799 | (_a = this._root) === null || _a === void 0 ? void 0 : _a.enqueueUpdate({
800 | path: `${this._path}.${"cn" /* Childnodes */}`,
801 | value: this.children.map(hydrate_1.hydrate),
802 | });
803 | }
804 | insertBefore(child, beforeChild) {
805 | var _a;
806 | if (!this.children)
807 | return;
808 | const index = this.children.findIndex((item) => item.id === beforeChild.id);
809 | if (index < 0)
810 | return;
811 | child.parentNode = this;
812 | this.children.splice(index, 0, child);
813 | (_a = this._root) === null || _a === void 0 ? void 0 : _a.enqueueUpdate({
814 | path: `${this._path}.${"cn" /* Childnodes */}`,
815 | value: this.children.map(hydrate_1.hydrate),
816 | });
817 | }
818 | removeChild(child) {
819 | var _a;
820 | if (!this.children)
821 | return;
822 | const index = this.children.findIndex((item) => item.id === child.id);
823 | if (index < 0)
824 | return;
825 | this.children.splice(index, 1);
826 | (_a = this._root) === null || _a === void 0 ? void 0 : _a.enqueueUpdate({
827 | path: `${this._path}.${"cn" /* Childnodes */}`,
828 | value: this.children.map(hydrate_1.hydrate),
829 | });
830 | }
831 | addEventListener(eventName, value) {
832 | this.__handlers.set(eventName, value);
833 | }
834 | removeEventListener(eventName) {
835 | this.__handlers.delete(eventName);
836 | }
837 | // 触发事件(忽略冒泡和捕获)
838 | // taro3内部会二次封装这个事件,处理冒泡等行为,此处简化
839 | dispatchEvent(eventName, e) {
840 | if (this.__handlers.has(eventName)) {
841 | const fn = this.__handlers.get(eventName);
842 | typeof fn === 'function' && fn(e);
843 | }
844 | }
845 | }
846 | exports.TaroElement = TaroElement;
847 | class TaroText extends TaroElement {
848 | constructor(params) {
849 | super(params);
850 | this.nodeName = '#text';
851 | }
852 | set textContext(text) {
853 | var _a;
854 | this.text = text;
855 | (_a = this._root) === null || _a === void 0 ? void 0 : _a.enqueueUpdate({
856 | path: `${this._path}.${"v" /* Text */}`,
857 | value: text,
858 | });
859 | }
860 | }
861 | exports.TaroText = TaroText;
862 | class TaroRootElement extends TaroElement {
863 | constructor(params) {
864 | super(params);
865 | this.updatePayloads = [];
866 | this.ctx = null;
867 | this.pendingUpdate = false;
868 | }
869 | get _root() {
870 | return this;
871 | }
872 | // 页面顶层root节点让_path返回顶层名称
873 | get _path() {
874 | return short_cut_1.RootName;
875 | }
876 | enqueueUpdate(payload) {
877 | this.updatePayloads.push(payload);
878 | console.warn('updatePayloads', [...this.updatePayloads]);
879 | if (!this.pendingUpdate && this.ctx !== null) {
880 | this.performUpdate();
881 | }
882 | }
883 | performUpdate() {
884 | this.pendingUpdate = true;
885 | // TODO 这里可以优化,将所有的复杂payloads合并为最小payloads,传给setData
886 | const elements = [];
887 | while (this.updatePayloads.length > 0) {
888 | const item = this.updatePayloads.shift();
889 | elements.push(item);
890 | }
891 | console.warn('setData before', elements, this.ctx);
892 | Promise.resolve().then(() => {
893 | var _a;
894 | while (elements.length > 0) {
895 | const item = elements.shift();
896 | const { path, value } = item;
897 | (_a = this.ctx) === null || _a === void 0 ? void 0 : _a.setData({ [path]: value }, () => {
898 | console.warn('setData end');
899 | });
900 | }
901 | this.pendingUpdate = false;
902 | });
903 | }
904 | }
905 | exports.TaroRootElement = TaroRootElement;
906 |
907 |
908 | /***/ }),
909 |
910 | /***/ "./src/util.ts":
911 | /*!*********************!*\
912 | !*** ./src/util.ts ***!
913 | \*********************/
914 | /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
915 |
916 |
917 | Object.defineProperty(exports, "__esModule", ({ value: true }));
918 | exports.isFunction = exports.NodeTypeMap = exports.noop = exports.generate = exports.reset = void 0;
919 | const interface_1 = __webpack_require__(/*! ./interface */ "./src/interface.ts");
920 | let instanceId = 0;
921 | function reset() {
922 | instanceId = 0;
923 | }
924 | exports.reset = reset;
925 | function generate() {
926 | const id = instanceId;
927 | instanceId += 1;
928 | return id;
929 | }
930 | exports.generate = generate;
931 | const noop = () => { };
932 | exports.noop = noop;
933 | exports.NodeTypeMap = {
934 | 'view': interface_1.NodeType.VIEW,
935 | 'text': interface_1.NodeType.TEXT,
936 | 'root': interface_1.NodeType.ROOT
937 | };
938 | const isFunction = (target) => typeof target === 'function';
939 | exports.isFunction = isFunction;
940 |
941 |
942 | /***/ })
943 |
944 | },
945 | /******/ __webpack_require__ => { // webpackRuntimeModules
946 | /******/ var __webpack_exec__ = (moduleId) => (__webpack_require__(__webpack_require__.s = moduleId))
947 | /******/ __webpack_require__.O(0, ["vendors"], () => (__webpack_exec__("./build/app.ts")));
948 | /******/ var __webpack_exports__ = __webpack_require__.O();
949 | /******/ }
950 | ]);
951 | //# sourceMappingURL=app.js.map
--------------------------------------------------------------------------------
/dist/pages/page-second/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | (tt["webpackChunkmini_taro"] = tt["webpackChunkmini_taro"] || []).push([["pages/page-second/index"],{
3 |
4 | /***/ "./build/page-second.ts":
5 | /*!******************************!*\
6 | !*** ./build/page-second.ts ***!
7 | \******************************/
8 | /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
9 |
10 |
11 | Object.defineProperty(exports, "__esModule", ({ value: true }));
12 | const page_second_1 = __webpack_require__(/*! ../demo/pages/page-second */ "./demo/pages/page-second/index.tsx");
13 | const index_1 = __webpack_require__(/*! @/index */ "./src/index.ts");
14 | Page((0, index_1.createPageConfig)(page_second_1.SecondPage, { root: { nn: '' } }, { path: 'pages/page-second/index' }));
15 |
16 |
17 | /***/ }),
18 |
19 | /***/ "./demo/component/test-comp.tsx":
20 | /*!**************************************!*\
21 | !*** ./demo/component/test-comp.tsx ***!
22 | \**************************************/
23 | /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
24 |
25 |
26 | // 普通function组件
27 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
28 | if (k2 === undefined) k2 = k;
29 | Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
30 | }) : (function(o, m, k, k2) {
31 | if (k2 === undefined) k2 = k;
32 | o[k2] = m[k];
33 | }));
34 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
35 | Object.defineProperty(o, "default", { enumerable: true, value: v });
36 | }) : function(o, v) {
37 | o["default"] = v;
38 | });
39 | var __importStar = (this && this.__importStar) || function (mod) {
40 | if (mod && mod.__esModule) return mod;
41 | var result = {};
42 | if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
43 | __setModuleDefault(result, mod);
44 | return result;
45 | };
46 | Object.defineProperty(exports, "__esModule", ({ value: true }));
47 | const react_1 = __importStar(__webpack_require__(/*! react */ "./node_modules/react/index.js"));
48 | const index_1 = __webpack_require__(/*! @/index */ "./src/index.ts");
49 | const TestComp = () => {
50 | const [content, set] = (0, react_1.useState)('(准备更新function组件)');
51 | (0, react_1.useEffect)(() => {
52 | // 2s 后更新数字
53 | const t1 = setTimeout(() => {
54 | set('(更新成功)');
55 | }, 2000);
56 | return () => {
57 | clearTimeout(t1);
58 | };
59 | }, []);
60 | return react_1.default.createElement(index_1.View, { style: { color: 'blue', marginTop: '10px', border: '1px solid red' } },
61 | react_1.default.createElement(index_1.Text, null, "\u8FD9\u91CC\u662Ffunction\u7EC4\u4EF6"),
62 | react_1.default.createElement(index_1.Text, null, content));
63 | };
64 | exports["default"] = TestComp;
65 |
66 |
67 | /***/ }),
68 |
69 | /***/ "./demo/pages/page-second/index.tsx":
70 | /*!******************************************!*\
71 | !*** ./demo/pages/page-second/index.tsx ***!
72 | \******************************************/
73 | /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
74 |
75 |
76 | // class组件页面
77 | var __importDefault = (this && this.__importDefault) || function (mod) {
78 | return (mod && mod.__esModule) ? mod : { "default": mod };
79 | };
80 | Object.defineProperty(exports, "__esModule", ({ value: true }));
81 | exports.SecondPage = void 0;
82 | const react_1 = __importDefault(__webpack_require__(/*! react */ "./node_modules/react/index.js"));
83 | const index_1 = __webpack_require__(/*! @/index */ "./src/index.ts");
84 | const test_comp_1 = __importDefault(__webpack_require__(/*! ../../component/test-comp */ "./demo/component/test-comp.tsx"));
85 | const util_1 = __webpack_require__(/*! ../../util */ "./demo/util.ts");
86 | class SecondPage extends react_1.default.Component {
87 | constructor(props) {
88 | super(props);
89 | this.state = {
90 | text: '你好呀,这里是第二个页面!'
91 | };
92 | }
93 | changeText() {
94 | this.setState({ text: '今天是好天气' });
95 | }
96 | returnEntry() {
97 | util_1.native.navigateBack();
98 | }
99 | render() {
100 | return react_1.default.createElement(index_1.View, { className: "wrapper", style: { marginTop: '20px', padding: '10px' } },
101 | react_1.default.createElement(index_1.Text, { style: { color: 'red' } }, this.state.text),
102 | react_1.default.createElement(index_1.Button, { style: { margin: '10px 0' }, type: "primary", onClick: () => this.changeText() }, "\u4FEE\u6539\u6587\u5B57"),
103 | react_1.default.createElement(index_1.Button, { type: "primary", onClick: () => this.returnEntry() }, "\u8FD4\u56DE\u4E0A\u9875\u9762"),
104 | react_1.default.createElement(test_comp_1.default, null));
105 | }
106 | }
107 | exports.SecondPage = SecondPage;
108 |
109 |
110 | /***/ }),
111 |
112 | /***/ "./demo/util.ts":
113 | /*!**********************!*\
114 | !*** ./demo/util.ts ***!
115 | \**********************/
116 | /***/ ((__unused_webpack_module, exports) => {
117 |
118 |
119 | // tt/wx/qq等顶层对象处理
120 | Object.defineProperty(exports, "__esModule", ({ value: true }));
121 | exports.native = void 0;
122 | exports.native = ck1() || ck2() || ck3() || ck4() || ck5() || ck6() || error();
123 | function ck1() { try {
124 | return tt;
125 | }
126 | catch (e) {
127 | return undefined;
128 | } }
129 | function ck2() { try {
130 | return wx;
131 | }
132 | catch (e) {
133 | return undefined;
134 | } }
135 | function ck3() { try {
136 | return qq;
137 | }
138 | catch (e) {
139 | return undefined;
140 | } }
141 | function ck4() { try {
142 | return jd;
143 | }
144 | catch (e) {
145 | return undefined;
146 | } }
147 | function ck5() { try {
148 | return my;
149 | }
150 | catch (e) {
151 | return undefined;
152 | } }
153 | function ck6() { try {
154 | return swan;
155 | }
156 | catch (e) {
157 | return undefined;
158 | } }
159 | function error() { throw new Error('未支持的小程序类型'); }
160 |
161 |
162 | /***/ }),
163 |
164 | /***/ "./src/createAppConfig.ts":
165 | /*!********************************!*\
166 | !*** ./src/createAppConfig.ts ***!
167 | \********************************/
168 | /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
169 |
170 |
171 | var __importDefault = (this && this.__importDefault) || function (mod) {
172 | return (mod && mod.__esModule) ? mod : { "default": mod };
173 | };
174 | Object.defineProperty(exports, "__esModule", ({ value: true }));
175 | exports.getTaroElementById = exports.getTaroRootElementByUid = exports.createAppConfig = exports.PageContext = void 0;
176 | const react_1 = __importDefault(__webpack_require__(/*! react */ "./node_modules/react/index.js"));
177 | const interface_1 = __webpack_require__(/*! ./interface */ "./src/interface.ts");
178 | const render_1 = __webpack_require__(/*! ./render */ "./src/render.ts");
179 | const taro_element_1 = __webpack_require__(/*! ./taro-element */ "./src/taro-element.ts");
180 | const RootElement = new taro_element_1.TaroElement({
181 | id: Infinity,
182 | props: {},
183 | nodeName: 'view',
184 | type: interface_1.NodeType.ROOT,
185 | });
186 | const h = react_1.default.createElement;
187 | const EMPTY_OBJ = {};
188 | exports.PageContext = EMPTY_OBJ;
189 | const connectReactPage = (id) => {
190 | return (component) => {
191 | if (exports.PageContext === EMPTY_OBJ) {
192 | exports.PageContext = react_1.default.createContext('');
193 | }
194 | return class Page extends react_1.default.Component {
195 | componentDidCatch(error, info) {
196 | if (true) {
197 | console.warn(error);
198 | console.error(info.componentStack);
199 | }
200 | }
201 | render() {
202 | // 名称是'root'的dom节点就是页面顶层节点了,在hostConfig中处理成不同节点
203 | return h('root', { pageId: id }, h(exports.PageContext.Provider, { value: id }, h(component, Object.assign({}, this.props))));
204 | }
205 | };
206 | };
207 | };
208 | const createAppConfig = (App) => {
209 | // 这个Wrapper就是一个代理,将小程序的更新代理到这个页面组件上,再将页面最近的更新setData回小程序
210 | class AppWrapper extends react_1.default.Component {
211 | constructor() {
212 | super(...arguments);
213 | this.pages = [];
214 | this.elements = [];
215 | }
216 | mount(component, id, cb) {
217 | const key = id;
218 | const page = () => h(component, { key, tid: id });
219 | this.pages.push(page);
220 | // 强制更新一次
221 | this.forceUpdate(cb);
222 | }
223 | unmount(id, cb) {
224 | const idx = this.elements.findIndex((item) => item.props.tid === id);
225 | this.elements.splice(idx, 1);
226 | this.forceUpdate(cb);
227 | }
228 | render() {
229 | while (this.pages.length > 0) {
230 | const page = this.pages.pop();
231 | this.elements.push(page());
232 | }
233 | const props = null;
234 | return react_1.default.createElement(App, props, this.elements.slice());
235 | }
236 | }
237 | const wrapper = (0, render_1.render)(react_1.default.createElement(AppWrapper), RootElement);
238 | const createConfig = () => {
239 | // app的配置不能是一个复杂对象。。
240 | const config = Object.create({
241 | mount: function (component, id, cb) {
242 | const page = connectReactPage(id)(component);
243 | wrapper.mount(page, id, cb);
244 | },
245 | unmount: function (id, cb) {
246 | wrapper.unmount(id, cb);
247 | },
248 | onLaunch: function (options) {
249 | console.warn('app onLaunch');
250 | },
251 | onShow: function (options) {
252 | //
253 | },
254 | onHide: function () {
255 | //
256 | },
257 | onError: function (msg) {
258 | console.log('app error', msg);
259 | },
260 | getTree: function () {
261 | return RootElement;
262 | },
263 | });
264 | return config;
265 | };
266 | return createConfig();
267 | };
268 | exports.createAppConfig = createAppConfig;
269 | // 页面顶层元素是TaroRootElement props内包含一个pageId,值是当前页面路径
270 | // 查找指定的页面root元素
271 | function getTaroRootElementByUid(rootElement, uid) {
272 | var _a;
273 | const queue = [];
274 | let target = undefined;
275 | // 广度优先查找
276 | queue.push(rootElement);
277 | while (queue.length > 0) {
278 | const t = queue.shift();
279 | if (t.getAttribute('pageId') === uid) {
280 | target = t;
281 | break;
282 | }
283 | else {
284 | (_a = t.children) === null || _a === void 0 ? void 0 : _a.map((item) => queue.push(item));
285 | }
286 | }
287 | return target;
288 | }
289 | exports.getTaroRootElementByUid = getTaroRootElementByUid;
290 | // 查找指定的页面内元素
291 | function getTaroElementById(rootElement, id) {
292 | var _a;
293 | const queue = [];
294 | let target = undefined;
295 | queue.push(rootElement);
296 | while (queue.length > 0) {
297 | const t = queue.shift();
298 | if (t.getAttribute('uid') === id) {
299 | target = t;
300 | break;
301 | }
302 | else {
303 | (_a = t.children) === null || _a === void 0 ? void 0 : _a.map((item) => queue.push(item));
304 | }
305 | }
306 | return target;
307 | }
308 | exports.getTaroElementById = getTaroElementById;
309 |
310 |
311 | /***/ }),
312 |
313 | /***/ "./src/createPageConfig.ts":
314 | /*!*********************************!*\
315 | !*** ./src/createPageConfig.ts ***!
316 | \*********************************/
317 | /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
318 |
319 |
320 | var __importDefault = (this && this.__importDefault) || function (mod) {
321 | return (mod && mod.__esModule) ? mod : { "default": mod };
322 | };
323 | Object.defineProperty(exports, "__esModule", ({ value: true }));
324 | exports.taroHook = exports.createPageConfig = void 0;
325 | const react_1 = __importDefault(__webpack_require__(/*! react */ "./node_modules/react/index.js"));
326 | const createAppConfig_1 = __webpack_require__(/*! ./createAppConfig */ "./src/createAppConfig.ts");
327 | const util_1 = __webpack_require__(/*! ./util */ "./src/util.ts");
328 | const createPageConfig = (Component, initData, pageConfig) => {
329 | const { path } = pageConfig;
330 | const pageUid = path;
331 | let app = null;
332 | try {
333 | app = getApp();
334 | }
335 | catch (e) {
336 | console.error(e);
337 | }
338 | const getPageElement = () => {
339 | const rootElement = app.getTree();
340 | return (0, createAppConfig_1.getTaroRootElementByUid)(rootElement, pageUid);
341 | };
342 | const getElement = (id) => {
343 | const rootElement = app.getTree();
344 | return (0, createAppConfig_1.getTaroElementById)(rootElement, id);
345 | };
346 | // 所有事件汇总到一个方法上
347 | const eventHandler = (e) => {
348 | // 这里使用currentTarget是为了避免被冒泡影响
349 | const { type: eventName, currentTarget = {} } = e || {};
350 | const { id = '' } = currentTarget;
351 | const pageElement = getPageElement();
352 | if (id && (pageElement === null || pageElement === void 0 ? void 0 : pageElement.ctx)) {
353 | const currentElement = getElement(id);
354 | if (!currentElement)
355 | return;
356 | currentElement.dispatchEvent(eventName, e);
357 | }
358 | };
359 | const createConfig = () => {
360 | const config = Object.create({
361 | data: initData,
362 | onLoad: function (options) {
363 | console.warn('page onLoad', options);
364 | // 小程序page实例
365 | const page = this;
366 | this.$taroPath = pageUid;
367 | app &&
368 | app.mount(Component, this.$taroPath, () => {
369 | const pageElement = getPageElement();
370 | if (pageElement) {
371 | pageElement.ctx = page;
372 | pageElement.performUpdate();
373 | }
374 | });
375 | },
376 | onShow: function () {
377 | safeExecute('onShow', pageUid);
378 | },
379 | onHide: function () {
380 | safeExecute('onHide', pageUid);
381 | },
382 | onReady: function () {
383 | safeExecute('onReady', pageUid);
384 | },
385 | onUnload: function () {
386 | app &&
387 | app.unmount(pageUid, () => {
388 | console.warn(`page: ${pageUid} unmount`);
389 | });
390 | },
391 | eh: eventHandler,
392 | });
393 | return config;
394 | };
395 | return createConfig();
396 | };
397 | exports.createPageConfig = createPageConfig;
398 | const hooks = new Map();
399 | function safeExecute(lifeCycle, uid) {
400 | // 这里可以将小程序生命周期hook执行
401 | if (!hooks.has(uid))
402 | return;
403 | const target = hooks.get(uid);
404 | const cb = target.get(lifeCycle);
405 | if (cb && (0, util_1.isFunction)(cb)) {
406 | cb.call(null);
407 | }
408 | }
409 | function taroHook(lifeCycle) {
410 | return (cb) => {
411 | // 这样拿到对应页面的uid
412 | const id = react_1.default.useContext(createAppConfig_1.PageContext);
413 | const cbRef = react_1.default.useRef(cb);
414 | react_1.default.useLayoutEffect(() => {
415 | if (!(0, util_1.isFunction)(cbRef.current))
416 | return;
417 | if (!hooks.has(id))
418 | hooks.set(id, new Map());
419 | const map = hooks.get(id);
420 | map.set(lifeCycle, cbRef.current);
421 | }, []);
422 | };
423 | }
424 | exports.taroHook = taroHook;
425 |
426 |
427 | /***/ }),
428 |
429 | /***/ "./src/hooks.ts":
430 | /*!**********************!*\
431 | !*** ./src/hooks.ts ***!
432 | \**********************/
433 | /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
434 |
435 |
436 | Object.defineProperty(exports, "__esModule", ({ value: true }));
437 | exports.useDidHide = exports.useDidShow = exports.useReady = void 0;
438 | /**
439 | * 这里实现的hook对应Taro提供的hook
440 | * 参考这些hook可以实现出任意需要的业务hook,比如useShareAppMessage,usePageScroll等
441 | */
442 | const createPageConfig_1 = __webpack_require__(/*! ./createPageConfig */ "./src/createPageConfig.ts");
443 | exports.useReady = (0, createPageConfig_1.taroHook)('onReady');
444 | exports.useDidShow = (0, createPageConfig_1.taroHook)('onShow');
445 | exports.useDidHide = (0, createPageConfig_1.taroHook)('onHide');
446 |
447 |
448 | /***/ }),
449 |
450 | /***/ "./src/host-config.ts":
451 | /*!****************************!*\
452 | !*** ./src/host-config.ts ***!
453 | \****************************/
454 | /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
455 |
456 |
457 | // 这边要借助 react-reconciler 实现一套虚拟dom树的系统
458 | var __importDefault = (this && this.__importDefault) || function (mod) {
459 | return (mod && mod.__esModule) ? mod : { "default": mod };
460 | };
461 | Object.defineProperty(exports, "__esModule", ({ value: true }));
462 | exports.ReactReconciler = exports.TaroReconciler = exports.updateProps = void 0;
463 | const react_reconciler_1 = __importDefault(__webpack_require__(/*! react-reconciler */ "./node_modules/react-reconciler/index.js"));
464 | exports.ReactReconciler = react_reconciler_1.default;
465 | const interface_1 = __webpack_require__(/*! ./interface */ "./src/interface.ts");
466 | const util_1 = __webpack_require__(/*! ./util */ "./src/util.ts");
467 | const taro_element_1 = __webpack_require__(/*! ./taro-element */ "./src/taro-element.ts");
468 | function updateProps(dom, oldProps, newProps) {
469 | let i;
470 | for (i in oldProps) {
471 | if (!(i in newProps)) {
472 | setProperty(dom, i, null, oldProps[i]);
473 | }
474 | }
475 | for (i in newProps) {
476 | if (oldProps[i] !== newProps[i]) {
477 | setProperty(dom, i, newProps[i], oldProps[i]);
478 | }
479 | }
480 | }
481 | exports.updateProps = updateProps;
482 | function isEventName(s) {
483 | return s[0] === 'o' && s[1] === 'n';
484 | }
485 | function setEvent(dom, name, value) {
486 | let eventName = name.toLowerCase().slice(2);
487 | if (eventName === 'click') {
488 | eventName = 'tap';
489 | }
490 | if (typeof value === 'function') {
491 | dom.addEventListener(eventName, value);
492 | }
493 | else {
494 | dom.removeEventListener(eventName);
495 | }
496 | }
497 | /**
498 | * 判断属性,处理属性
499 | */
500 | function setProperty(dom, name, value, oldValue) {
501 | // className转class
502 | name = name === 'className' ? 'class' : name;
503 | if (name === 'key' ||
504 | name === 'children' ||
505 | name === 'ref') {
506 | // 跳过
507 | }
508 | else if (name === 'style') {
509 | // TODO 需要处理style的变化
510 | }
511 | else if (isEventName(name)) {
512 | // 事件 onClick等
513 | setEvent(dom, name, value);
514 | }
515 | else if (value == null) {
516 | dom.removeAttribute(name);
517 | }
518 | else {
519 | dom.setAttribute(name, value);
520 | }
521 | }
522 | const hostConfig = {
523 | createInstance(type, props, rootContainer, hostContext, internalHandle) {
524 | const conf = {
525 | id: (0, util_1.generate)(),
526 | type: util_1.NodeTypeMap[type] || interface_1.NodeType.VIEW,
527 | nodeName: type,
528 | props,
529 | children: [],
530 | };
531 | if (type === 'root') {
532 | return new taro_element_1.TaroRootElement(conf);
533 | }
534 | else {
535 | return new taro_element_1.TaroElement(conf);
536 | }
537 | },
538 | createTextInstance(text, rootContainer, hostContext, internalHandle) {
539 | const vnode = new taro_element_1.TaroText({
540 | id: (0, util_1.generate)(),
541 | type: interface_1.NodeType.TEXT,
542 | nodeName: 'text',
543 | props: {},
544 | text,
545 | });
546 | return vnode;
547 | },
548 | getPublicInstance(inst) {
549 | return inst;
550 | },
551 | getRootHostContext() {
552 | return {};
553 | },
554 | getChildHostContext() {
555 | return {};
556 | },
557 | appendChild(parent, child) {
558 | parent.appendChild(child);
559 | },
560 | appendInitialChild(parent, child) {
561 | parent.appendChild(child);
562 | },
563 | appendChildToContainer(parent, child) {
564 | parent.appendChild(child);
565 | },
566 | removeChild(parent, child) {
567 | parent.removeChild(child);
568 | },
569 | removeChildFromContainer(parent, child) {
570 | parent.removeChild(child);
571 | },
572 | insertBefore(parent, child, refChild) {
573 | parent.insertBefore(child, refChild);
574 | },
575 | insertInContainerBefore(parent, child, refChild) {
576 | parent.insertBefore(child, refChild);
577 | },
578 | commitTextUpdate(textInst, _, newText) {
579 | textInst.textContext = newText;
580 | },
581 | finalizeInitialChildren(dom, _, props) {
582 | updateProps(dom, {}, props);
583 | return false;
584 | },
585 | prepareUpdate() {
586 | return [];
587 | },
588 | commitUpdate(dom, _payload, _type, oldProps, newProps) {
589 | updateProps(dom, oldProps, newProps);
590 | },
591 | clearContainer(element) {
592 | if (element.children && element.children.length > 0) {
593 | element.children = [];
594 | }
595 | },
596 | shouldSetTextContent: () => false,
597 | prepareForCommit(..._) { return null; },
598 | resetAfterCommit: util_1.noop,
599 | commitMount: util_1.noop,
600 | now: () => Date.now(),
601 | cancelTimeout: clearTimeout,
602 | scheduleTimeout: setTimeout,
603 | preparePortalMount: util_1.noop,
604 | noTimeout: -1,
605 | supportsMutation: true,
606 | supportsPersistence: false,
607 | isPrimaryRenderer: true,
608 | supportsHydration: false
609 | };
610 | const TaroReconciler = (0, react_reconciler_1.default)(hostConfig);
611 | exports.TaroReconciler = TaroReconciler;
612 |
613 |
614 | /***/ }),
615 |
616 | /***/ "./src/hydrate.ts":
617 | /*!************************!*\
618 | !*** ./src/hydrate.ts ***!
619 | \************************/
620 | /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
621 |
622 |
623 | var __rest = (this && this.__rest) || function (s, e) {
624 | var t = {};
625 | for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
626 | t[p] = s[p];
627 | if (s != null && typeof Object.getOwnPropertySymbols === "function")
628 | for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
629 | if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
630 | t[p[i]] = s[p[i]];
631 | }
632 | return t;
633 | };
634 | Object.defineProperty(exports, "__esModule", ({ value: true }));
635 | exports.styleTransform = exports.hydrate = exports.generateUid = void 0;
636 | const interface_1 = __webpack_require__(/*! ./interface */ "./src/interface.ts");
637 | const isEmpty = (children) => {
638 | return !children || (Array.isArray(children) && children.length === 0);
639 | };
640 | const generateUid = (id) => `u-${id}`;
641 | exports.generateUid = generateUid;
642 | /**
643 | *
644 | * 这个函数是将虚拟dom树转为了渲染属性树
645 | * @param node
646 | * @returns
647 | */
648 | const hydrate = (node) => {
649 | if (node.type === interface_1.NodeType.TEXT && isEmpty(node.children)) {
650 | return {
651 | ["v" /* Text */]: node.text || '',
652 | ["nn" /* NodeName */]: node.nodeName,
653 | };
654 | }
655 | else {
656 | const { nodeName, props, id, children } = node;
657 | const _a = props || {}, { className, style, children: _children } = _a, nextProps = __rest(_a, ["className", "style", "children"]);
658 | // style字符串化 + 驼峰转横线
659 | let styleContent = '';
660 | if (!!style) {
661 | for (const [key, value] of Object.entries(style)) {
662 | styleContent += `${(0, exports.styleTransform)(key)}: ${value};`;
663 | }
664 | }
665 | return Object.assign({ ["cn" /* Childnodes */]: !!children && Array.isArray(children) ? children.map(exports.hydrate) : [], ["nn" /* NodeName */]: nodeName, ["cl" /* Class */]: className || '', ["st" /* Style */]: styleContent, uid: (0, exports.generateUid)(id) }, nextProps);
666 | }
667 | };
668 | exports.hydrate = hydrate;
669 | // fontSize -> font-size
670 | const styleTransform = (name) => {
671 | const list = name.split('');
672 | const index = list.findIndex(p => /[A-Z]/.test(p));
673 | if (index >= 0)
674 | list.splice(index, 1, '-' + list[index].toLowerCase());
675 | return list.join('');
676 | };
677 | exports.styleTransform = styleTransform;
678 |
679 |
680 | /***/ }),
681 |
682 | /***/ "./src/index.ts":
683 | /*!**********************!*\
684 | !*** ./src/index.ts ***!
685 | \**********************/
686 | /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
687 |
688 |
689 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
690 | if (k2 === undefined) k2 = k;
691 | Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
692 | }) : (function(o, m, k, k2) {
693 | if (k2 === undefined) k2 = k;
694 | o[k2] = m[k];
695 | }));
696 | var __exportStar = (this && this.__exportStar) || function(m, exports) {
697 | for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
698 | };
699 | Object.defineProperty(exports, "__esModule", ({ value: true }));
700 | exports.createAppConfig = exports.createPageConfig = void 0;
701 | var createPageConfig_1 = __webpack_require__(/*! ./createPageConfig */ "./src/createPageConfig.ts");
702 | Object.defineProperty(exports, "createPageConfig", ({ enumerable: true, get: function () { return createPageConfig_1.createPageConfig; } }));
703 | var createAppConfig_1 = __webpack_require__(/*! ./createAppConfig */ "./src/createAppConfig.ts");
704 | Object.defineProperty(exports, "createAppConfig", ({ enumerable: true, get: function () { return createAppConfig_1.createAppConfig; } }));
705 | __exportStar(__webpack_require__(/*! ./hooks */ "./src/hooks.ts"), exports);
706 | __exportStar(__webpack_require__(/*! ./native-components */ "./src/native-components.ts"), exports);
707 |
708 |
709 | /***/ }),
710 |
711 | /***/ "./src/interface.ts":
712 | /*!**************************!*\
713 | !*** ./src/interface.ts ***!
714 | \**************************/
715 | /***/ ((__unused_webpack_module, exports) => {
716 |
717 |
718 | // import { VNode } from "./vnode"
719 | Object.defineProperty(exports, "__esModule", ({ value: true }));
720 | exports.NodeType = void 0;
721 | var NodeType;
722 | (function (NodeType) {
723 | NodeType[NodeType["ROOT"] = 0] = "ROOT";
724 | NodeType[NodeType["TEXT"] = 1] = "TEXT";
725 | NodeType[NodeType["VIEW"] = 2] = "VIEW";
726 | })(NodeType || (NodeType = {}));
727 | exports.NodeType = NodeType;
728 |
729 |
730 | /***/ }),
731 |
732 | /***/ "./src/native-components.ts":
733 | /*!**********************************!*\
734 | !*** ./src/native-components.ts ***!
735 | \**********************************/
736 | /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
737 |
738 |
739 | // 基础组件实现
740 | // 原生组件实际就是react组件,名称上与小程序组件对齐
741 | var __rest = (this && this.__rest) || function (s, e) {
742 | var t = {};
743 | for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
744 | t[p] = s[p];
745 | if (s != null && typeof Object.getOwnPropertySymbols === "function")
746 | for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
747 | if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
748 | t[p[i]] = s[p[i]];
749 | }
750 | return t;
751 | };
752 | var __importDefault = (this && this.__importDefault) || function (mod) {
753 | return (mod && mod.__esModule) ? mod : { "default": mod };
754 | };
755 | Object.defineProperty(exports, "__esModule", ({ value: true }));
756 | exports.Input = exports.Button = exports.Text = exports.View = void 0;
757 | const react_1 = __importDefault(__webpack_require__(/*! react */ "./node_modules/react/index.js"));
758 | const createNativeComponent = (name, props) => {
759 | return (params) => {
760 | const _a = params || {}, { children } = _a, nextParams = __rest(_a, ["children"]);
761 | return react_1.default.createElement(name, Object.assign({}, nextParams), children);
762 | };
763 | };
764 | exports.View = createNativeComponent('view', {});
765 | exports.Text = createNativeComponent('text', {});
766 | exports.Button = createNativeComponent('button', {});
767 | exports.Input = createNativeComponent('input', {});
768 |
769 |
770 | /***/ }),
771 |
772 | /***/ "./src/render.ts":
773 | /*!***********************!*\
774 | !*** ./src/render.ts ***!
775 | \***********************/
776 | /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
777 |
778 |
779 | // 这边要借助 react-reconciler 实现一套虚拟dom树的系统
780 | Object.defineProperty(exports, "__esModule", ({ value: true }));
781 | exports.render = void 0;
782 | const host_config_1 = __webpack_require__(/*! ./host-config */ "./src/host-config.ts");
783 | const render = (component, container) => {
784 | if (!container._rootContainer) {
785 | container._rootContainer = host_config_1.TaroReconciler.createContainer(container, 0, false, null);
786 | }
787 | host_config_1.TaroReconciler.updateContainer(component, container._rootContainer, null);
788 | return host_config_1.TaroReconciler.getPublicRootInstance(container._rootContainer);
789 | };
790 | exports.render = render;
791 |
792 |
793 | /***/ }),
794 |
795 | /***/ "./src/short-cut.ts":
796 | /*!**************************!*\
797 | !*** ./src/short-cut.ts ***!
798 | \**************************/
799 | /***/ ((__unused_webpack_module, exports) => {
800 |
801 |
802 | Object.defineProperty(exports, "__esModule", ({ value: true }));
803 | exports.Short = exports.RootName = void 0;
804 | exports.RootName = 'root';
805 | // 这里使用Taro的缩写
806 | var Short;
807 | (function (Short) {
808 | Short["Container"] = "container";
809 | Short["Childnodes"] = "cn";
810 | Short["Text"] = "v";
811 | Short["NodeType"] = "nt";
812 | Short["NodeName"] = "nn";
813 | Short["Style"] = "st";
814 | Short["Class"] = "cl";
815 | Short["Src"] = "src";
816 | })(Short = exports.Short || (exports.Short = {}));
817 |
818 |
819 | /***/ }),
820 |
821 | /***/ "./src/taro-element.ts":
822 | /*!*****************************!*\
823 | !*** ./src/taro-element.ts ***!
824 | \*****************************/
825 | /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
826 |
827 |
828 | Object.defineProperty(exports, "__esModule", ({ value: true }));
829 | exports.TaroRootElement = exports.TaroText = exports.TaroElement = void 0;
830 | const short_cut_1 = __webpack_require__(/*! ./short-cut */ "./src/short-cut.ts");
831 | const hydrate_1 = __webpack_require__(/*! ./hydrate */ "./src/hydrate.ts");
832 | class TaroElement {
833 | constructor(params) {
834 | this.parentNode = null;
835 | this.__handlers = new Map();
836 | const { id, type, nodeName, props, children, text } = params;
837 | this.id = id;
838 | this.type = type;
839 | this.nodeName = nodeName;
840 | this.props = Object.assign({ uid: (0, hydrate_1.generateUid)(id) }, props);
841 | // props中的children是冗余数据
842 | if (this.props['children']) {
843 | delete this.props['children'];
844 | }
845 | this.children = children;
846 | this.text = text;
847 | }
848 | onAttributeUpdate() {
849 | var _a, _b, _c;
850 | if (!this.parentNode)
851 | return;
852 | const index = (_a = this.parentNode.children) === null || _a === void 0 ? void 0 : _a.findIndex((p) => p.id === this.id);
853 | if (!index || index < 0)
854 | return;
855 | (_b = this._root) === null || _b === void 0 ? void 0 : _b.enqueueUpdate({
856 | path: `${(_c = this.parentNode) === null || _c === void 0 ? void 0 : _c._path}.${"cn" /* Childnodes */}[${index}]`,
857 | value: (0, hydrate_1.hydrate)(this),
858 | });
859 | }
860 | setAttribute(name, value) {
861 | this.props[name] = value;
862 | this.onAttributeUpdate();
863 | }
864 | getAttribute(name) {
865 | return this.props[name];
866 | }
867 | removeAttribute(name) {
868 | if (name in this.props) {
869 | delete this.props[name];
870 | this.onAttributeUpdate();
871 | }
872 | }
873 | get _path() {
874 | const parentNode = this.parentNode;
875 | if (!parentNode || !Array.isArray(parentNode.children))
876 | return '';
877 | const index = parentNode.children.findIndex((p) => p.id === this.id);
878 | return `${parentNode._path}.${"cn" /* Childnodes */}[${index}]`;
879 | }
880 | get _root() {
881 | var _a;
882 | return ((_a = this.parentNode) === null || _a === void 0 ? void 0 : _a._root) || null;
883 | }
884 | appendChild(child) {
885 | var _a;
886 | child.parentNode = this;
887 | if (!this.children) {
888 | this.children = [child];
889 | }
890 | else {
891 | this.children.push(child);
892 | }
893 | (_a = this._root) === null || _a === void 0 ? void 0 : _a.enqueueUpdate({
894 | path: `${this._path}.${"cn" /* Childnodes */}`,
895 | value: this.children.map(hydrate_1.hydrate),
896 | });
897 | }
898 | insertBefore(child, beforeChild) {
899 | var _a;
900 | if (!this.children)
901 | return;
902 | const index = this.children.findIndex((item) => item.id === beforeChild.id);
903 | if (index < 0)
904 | return;
905 | child.parentNode = this;
906 | this.children.splice(index, 0, child);
907 | (_a = this._root) === null || _a === void 0 ? void 0 : _a.enqueueUpdate({
908 | path: `${this._path}.${"cn" /* Childnodes */}`,
909 | value: this.children.map(hydrate_1.hydrate),
910 | });
911 | }
912 | removeChild(child) {
913 | var _a;
914 | if (!this.children)
915 | return;
916 | const index = this.children.findIndex((item) => item.id === child.id);
917 | if (index < 0)
918 | return;
919 | this.children.splice(index, 1);
920 | (_a = this._root) === null || _a === void 0 ? void 0 : _a.enqueueUpdate({
921 | path: `${this._path}.${"cn" /* Childnodes */}`,
922 | value: this.children.map(hydrate_1.hydrate),
923 | });
924 | }
925 | addEventListener(eventName, value) {
926 | this.__handlers.set(eventName, value);
927 | }
928 | removeEventListener(eventName) {
929 | this.__handlers.delete(eventName);
930 | }
931 | // 触发事件(忽略冒泡和捕获)
932 | // taro3内部会二次封装这个事件,处理冒泡等行为,此处简化
933 | dispatchEvent(eventName, e) {
934 | if (this.__handlers.has(eventName)) {
935 | const fn = this.__handlers.get(eventName);
936 | typeof fn === 'function' && fn(e);
937 | }
938 | }
939 | }
940 | exports.TaroElement = TaroElement;
941 | class TaroText extends TaroElement {
942 | constructor(params) {
943 | super(params);
944 | this.nodeName = '#text';
945 | }
946 | set textContext(text) {
947 | var _a;
948 | this.text = text;
949 | (_a = this._root) === null || _a === void 0 ? void 0 : _a.enqueueUpdate({
950 | path: `${this._path}.${"v" /* Text */}`,
951 | value: text,
952 | });
953 | }
954 | }
955 | exports.TaroText = TaroText;
956 | class TaroRootElement extends TaroElement {
957 | constructor(params) {
958 | super(params);
959 | this.updatePayloads = [];
960 | this.ctx = null;
961 | this.pendingUpdate = false;
962 | }
963 | get _root() {
964 | return this;
965 | }
966 | // 页面顶层root节点让_path返回顶层名称
967 | get _path() {
968 | return short_cut_1.RootName;
969 | }
970 | enqueueUpdate(payload) {
971 | this.updatePayloads.push(payload);
972 | console.warn('updatePayloads', [...this.updatePayloads]);
973 | if (!this.pendingUpdate && this.ctx !== null) {
974 | this.performUpdate();
975 | }
976 | }
977 | performUpdate() {
978 | this.pendingUpdate = true;
979 | // TODO 这里可以优化,将所有的复杂payloads合并为最小payloads,传给setData
980 | const elements = [];
981 | while (this.updatePayloads.length > 0) {
982 | const item = this.updatePayloads.shift();
983 | elements.push(item);
984 | }
985 | console.warn('setData before', elements, this.ctx);
986 | Promise.resolve().then(() => {
987 | var _a;
988 | while (elements.length > 0) {
989 | const item = elements.shift();
990 | const { path, value } = item;
991 | (_a = this.ctx) === null || _a === void 0 ? void 0 : _a.setData({ [path]: value }, () => {
992 | console.warn('setData end');
993 | });
994 | }
995 | this.pendingUpdate = false;
996 | });
997 | }
998 | }
999 | exports.TaroRootElement = TaroRootElement;
1000 |
1001 |
1002 | /***/ }),
1003 |
1004 | /***/ "./src/util.ts":
1005 | /*!*********************!*\
1006 | !*** ./src/util.ts ***!
1007 | \*********************/
1008 | /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
1009 |
1010 |
1011 | Object.defineProperty(exports, "__esModule", ({ value: true }));
1012 | exports.isFunction = exports.NodeTypeMap = exports.noop = exports.generate = exports.reset = void 0;
1013 | const interface_1 = __webpack_require__(/*! ./interface */ "./src/interface.ts");
1014 | let instanceId = 0;
1015 | function reset() {
1016 | instanceId = 0;
1017 | }
1018 | exports.reset = reset;
1019 | function generate() {
1020 | const id = instanceId;
1021 | instanceId += 1;
1022 | return id;
1023 | }
1024 | exports.generate = generate;
1025 | const noop = () => { };
1026 | exports.noop = noop;
1027 | exports.NodeTypeMap = {
1028 | 'view': interface_1.NodeType.VIEW,
1029 | 'text': interface_1.NodeType.TEXT,
1030 | 'root': interface_1.NodeType.ROOT
1031 | };
1032 | const isFunction = (target) => typeof target === 'function';
1033 | exports.isFunction = isFunction;
1034 |
1035 |
1036 | /***/ })
1037 |
1038 | },
1039 | /******/ __webpack_require__ => { // webpackRuntimeModules
1040 | /******/ var __webpack_exec__ = (moduleId) => (__webpack_require__(__webpack_require__.s = moduleId))
1041 | /******/ __webpack_require__.O(0, ["vendors"], () => (__webpack_exec__("./build/page-second.ts")));
1042 | /******/ var __webpack_exports__ = __webpack_require__.O();
1043 | /******/ }
1044 | ]);
1045 | //# sourceMappingURL=index.js.map
--------------------------------------------------------------------------------