成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

如何把 alibaba Rax 組件轉(zhuǎn)換到 React 下

Hydrogen / 3340人閱讀

摘要:如何轉(zhuǎn)換知道了二者的不同,那么如何轉(zhuǎn)換我們也就有方向了。因?yàn)橄旅總€(gè)元件本身就是一個(gè)普通的組件,我們可以通過直接把他們當(dāng)作其他組件轉(zhuǎn)換為的代碼來使用。至于如何把這個(gè)放到上,我們放到后面一起說。

背景
最近接手公司的一個(gè)移動(dòng)端項(xiàng)目,是通過 Rax 作為 dsl 開發(fā)的,在發(fā)布的時(shí)候構(gòu)建多分代碼,在 APP 端編譯為能夠運(yùn)行在 weex 上的代碼,在 H5(跑在瀏覽器或者 webview 里面,不管什么技術(shù)我們統(tǒng)稱 H5) 端使用降級的 weex

這一套開發(fā)體系,看起來很完美,一次開發(fā),三端運(yùn)行。但是真實(shí)在開發(fā)的時(shí)候,就不是這么完美了。由于畢竟是跑在 weex 上的,而不是瀏覽器。所以在開發(fā)方式上也很難直接從 web 端的開發(fā)方式平移過去,為了實(shí)現(xiàn)跨端運(yùn)行,所以在樣式上只實(shí)現(xiàn)了 Css 的子集, DOM API 也是如此,開發(fā)的時(shí)候,在瀏覽器里面調(diào)試的時(shí)候,一切正常,但是等發(fā)布到 APP 端用 weex 跑的時(shí)候又是各種問題,開發(fā)體驗(yàn)很不流暢。

當(dāng)然,有人會說那是因?yàn)槟銓?weex 的 api 不了解,也對,一直以來對與這種自己搞一套非標(biāo)準(zhǔn)體系來實(shí)現(xiàn)各種魔法功能的東西都不怎么感興趣 但是如果了解它的成本大于了它帶來的收益,那么對我們來說,就沒有必要做這件事情。

weex 相對于 H5 ,最大的優(yōu)點(diǎn)在于交互性能上要更好一點(diǎn)。

而隨著手機(jī)性能的提升,以及 webview 的不斷優(yōu)化,H5 的頁面也越來越流暢了,尤其是純展示形頁面上。而且相較于 H5 ,weex 天生不具備 seo 能力,同時(shí)存在分享傳播困難的缺點(diǎn),這樣看來,使用 weex 的理由就更少了。而且我們在一個(gè)新業(yè)務(wù)上使用 H5 開發(fā)了一個(gè)頁面,借助同構(gòu)以及提前緩存的能力,已經(jīng)把首屏打開速度做到了全球秒開,而且業(yè)務(wù)數(shù)據(jù)也達(dá)到預(yù)期,所以我們打算把現(xiàn)有的存量業(yè)務(wù)都遷移到 H5 上。

這就是我為什么要把基于 Rax 開發(fā)的模塊代碼轉(zhuǎn)換為 React 代碼,也就有了本篇文章。
本文針對的 rax 版本是 0.6.8 ,1.x 的版本改動(dòng)很大,不在本文討論范圍內(nèi)。

期望的目標(biāo)
對于一個(gè) rax 模塊,我們期望通過編譯后:

能夠在 react 下運(yùn)行
盡可能提取樣式到 css 文件里,不使用內(nèi)聯(lián)
不同之處
Rax 在開發(fā)之處,就是為了能夠使用 React 的語法來開發(fā) weex ,所以一開始在語法上和 React 幾乎一致。后續(xù)隨著 rax 的不斷迭代,漸漸和 react 有了一些不一樣的地方,但是差距不大。
我們對比一下同一個(gè)模塊在 rax 和 react 的實(shí)現(xiàn)代碼:

rax module

import { Component, createElement, findDOMNode } from "rax";
import Text from "rax-text";
import View from "rax-view";
import styles from "./index.css";

class Kisnows extends Component {
constructor(props) {

super(props);
this.state = {
  count: 1
};

}

handleClick = () => {

this.setState({
  count: this.state.count + 1
});

};

render() {

const { count } = this.state;
const { name } = this.props;
return (
  
    {name}
    
      怕什么真理無窮,進(jìn)一步有進(jìn)一步的好。
    
    點(diǎn)擊進(jìn)步:{count}
  
);

}
}

export default Kisnows;
react module

import { Component } from "react";
import { findDOMNode } from "react-dom";
import styles from "./index.css";

class Kisnows extends Component {
constructor(props) {

super(props);
this.state = {
  count: 1
};

}

handleClick = () => {

this.setState({
  count: this.state.count + 1
});

};

render() {

const { count } = this.state;
const { name } = this.props;
return (
  

{name}

怕什么真理無窮,進(jìn)一步有進(jìn)一步的好。
點(diǎn)擊進(jìn)步:{count}
);

}
}

export default Kisnows;
可以看到他們的區(qū)別如下:

引用的框架不同

這個(gè)當(dāng)然是廢話,
rax 模塊從 rax 上引入 Component, createElement 等組件的, react 模塊從 react 上引入。

還有一點(diǎn)的不同的地方,就是 findDOMNode , rax 上是直接掛載在 rax 模塊上的,而 findDOMNode 從 react-dom 上獲取。

使用的基礎(chǔ)元件不同

rax 本身是為了使用 react 語法去寫 weex 的而誕生的,weex 為了兼容 ios 和 android ,所以在兩個(gè)系統(tǒng)中抽了一些組件出來,在上面做了一層適配。

而 rax 為了跨 Web 和 Native 在 weex 之上又包了一層,于是就有了 Text, View, Image 等基礎(chǔ)元件,而無法使用普通 Web 開發(fā)使用的 span, div, img 等 html 標(biāo)簽。

樣式使用的不同

rax 下所有的樣式文件都是 css in js ,即使后面通過構(gòu)建等支持了引用外部 css 文件的樣式,但依然還是 css in js ,所有的樣式都是內(nèi)聯(lián)的。

同時(shí)從上面的代碼可以看到, rax 不知道從哪個(gè)版本開始支持了 style 屬性傳入 Array 類型。這一塊和 react 下不一致,react 只支持 Object 類型。

而一旦傳入的是個(gè) Array ,js 又是動(dòng)態(tài)語言,所以無法在編譯時(shí)判斷里面元素的類型,就不好判斷里面的元素哪些是要轉(zhuǎn)換為 class ,哪些是直接內(nèi)聯(lián)的樣式了。我們轉(zhuǎn)換上很多的精力也是花在了這里。

如何轉(zhuǎn)換
知道了二者的不同,那么如何轉(zhuǎn)換我們也就有方向了。

對代碼進(jìn)行轉(zhuǎn)換,現(xiàn)在最常見的方法就是基于 babel 的插件來進(jìn)行,首先把代碼轉(zhuǎn)換成 ast 語法樹,然后在此基礎(chǔ)上對 ast 進(jìn)行轉(zhuǎn)換,最后把 ast 轉(zhuǎn)換成我們需要的代碼。

通過開發(fā) babel 插件來轉(zhuǎn)換代碼,在現(xiàn)在的前端開發(fā)中很常見,我也不做過多介紹。但之前我也僅限于使用上, 沒有自己開發(fā)過,這次也是現(xiàn)學(xué)現(xiàn)賣。

介紹幾個(gè)學(xué)習(xí)中參考的文檔和工具:

babel-handbook
幾乎涵蓋開發(fā) babel 插件需要的一切。
AST Explorer
一個(gè)在線進(jìn)行語法解析的網(wǎng)站,可以實(shí)時(shí)驗(yàn)證你的想法。
由于 babel-handbook 已經(jīng)對如何開發(fā) babel 插件講解的很詳細(xì)了,對一些基本概念本文就不再贅述,默認(rèn)往下看的讀者都已經(jīng)有了這些知識基礎(chǔ)。

引用的框架不同
這個(gè)很好解決,我們需要的就是把:

import { Component, createElement, PureComponent, findDOMNode } from "rax";
這樣的代碼轉(zhuǎn)換成:

import { Component, createElement, PureComponent } from "react";
import { findDOMNode } from "react-dom";
看起來很簡單是吧,我們打開 AST Explorer ,在這上面輸入

import { Component, createElement, PureComponent, findDOMNode } from "rax";
會得到如下結(jié)果:

import
圖中左側(cè)的 import 語句轉(zhuǎn)換成 ast 后就是圖中右側(cè)紅框中內(nèi)容。
整個(gè) import 語句是一個(gè) ImportDeclaration , 引入的每個(gè)方法 Component、 createElement 等對應(yīng)一個(gè) ImportSpecifier 節(jié)點(diǎn)。
我們要做的就是把 ImportDeclaration.source.value 改為 react, 然后把 findDOMNode 從 rax 中抽出來,改為從 react-dom 里面引入就好了。

具體到操作上,就是我們在 visitor 里面添加對 ImportDeclaration 類型節(jié)點(diǎn)的遍歷:

visitor: {
ImportDeclaration(path) {

// 對于不符合我們要修改條件的節(jié)點(diǎn),直接 return ,節(jié)省無用遞歸調(diào)用的時(shí)間
if (
  path.node.source.value !== "rax" ||
  path.node.source.type !== "StringLiteral"
) {
  return;
}

}
}
然后區(qū)分哪些模塊是要從 react 里面引用的,哪些是需要從 react-dom 里面引用的,再對 ImportSpecifier 節(jié)點(diǎn)進(jìn)行遍歷,找到里面符合我們符合條件的模塊,方便后面生成新的 import 語句。

visitor: {
ImportDeclaration(path) {

if (
  path.node.source.value !== "rax" ||
  path.node.source.type !== "StringLiteral"
) {
  return;
}
const REACT_METHODS = [
  "createElement",
  "Component",
  "PureComponent",
  "PropTypes"
];
const REACT_DOM_METHODS = ["findDOMNode"];
const reactMethods = new Set();
const reactDOMMethods = new Set();
path.traverse({
  ImportSpecifier(importSpecifierPath) {
    importSpecifierPath.traverse({
      Identifier(identifierPath) {
        const methodName = identifierPath.node.name;
        // console.log("importSpecifierPath:Identifier:methodName", methodName)
        if (REACT_DOM_METHODS.includes(methodName)) {
          reactDOMMethods.add(methodName);
        } else if (REACT_METHODS.includes(methodName)) {
          reactMethods.add(methodName);
        } else {
          reactMethods.add(methodName);
          console.warn(
            `當(dāng)前方法 ${methodName} 沒有進(jìn)行配置,直接從React上獲取,如有問題請檢查此方法。`
          );
        }
      }
    });
  }
});
},

}
最后一步,用前面找到的 react 和 react-dom 的模塊,借助 bable 提供 template 來重新生成 import 語句,并刪除原有對 rax 的引用。

visitor: {
ImportDeclaration(path) {

if (
  path.node.source.value !== "rax" ||
  path.node.source.type !== "StringLiteral"
) {
  return;
}
const REACT_METHODS = [
  "createElement",
  "Component",
  "PureComponent",
  "PropTypes"
];
const REACT_DOM_METHODS = ["findDOMNode"];
const reactMethods = new Set();
const reactDOMMethods = new Set();
path.traverse({
  ImportSpecifier(importSpecifierPath) {
    importSpecifierPath.traverse({
      Identifier(identifierPath) {
        const methodName = identifierPath.node.name;
        // console.log("importSpecifierPath:Identifier:methodName", methodName)
        if (REACT_DOM_METHODS.includes(methodName)) {
          reactDOMMethods.add(methodName);
        } else if (REACT_METHODS.includes(methodName)) {
          reactMethods.add(methodName);
        } else {
          reactMethods.add(methodName);
          console.warn(
            `當(dāng)前方法 ${methodName} 沒有進(jìn)行配置,直接從React上獲取,如有問題請檢查此方法。`
          );
        }
      }
    });
  }
});
// 使用前面的 reactMethods 和 reactDOMMethods ,來生成新的 import 語句。
const importReactTemplate = template.ast(`
      import {${Array.from(reactMethods).join(",")} } from "react";
    `);
const importReactDOMTemplate = template.ast(`
      import { ${Array.from(reactDOMMethods).join(
        ","
      )}  } from "react-dom";
    `);
// 插入到當(dāng)前 path 前面
path.insertBefore(importReactTemplate);
path.insertBefore(importReactDOMTemplate);
// 刪除當(dāng)前 path ,也就是 rax 的 import 語句
path.remove();

},
}
這樣,我們就完成了引入模塊的轉(zhuǎn)換,如圖:

import
使用的基礎(chǔ)元件不同
對于基礎(chǔ)元件這一塊,我們可以不做任何處理。因?yàn)?rax 下每個(gè)元件本身就是一個(gè)普通的 rax 組件,我們可以通過 babel 直接把他們當(dāng)作其他 rax 組件轉(zhuǎn)換為 react 的代碼來使用。

但是我們轉(zhuǎn)換后的代碼只需要在 Web 上跑,而如果你看過基礎(chǔ)元件比如用 rax-text 舉例的代碼:rax-text 。
里面有很多代碼是僅在 weex 下運(yùn)行的,我們根本不需要,所以可以通過一些粗暴的方法來精簡這些元件。直接使用 Webpack 的 alias ,來把對 rax-text 等基礎(chǔ)元件的引用直接指定到我們的自己的一個(gè)組件下,比如我們自己就把以下元件加入到了 webpack 的 alias 里:

resolve: {
modules: ["node_modules"],
extensions: [".json", ".js", ".jsx"],
alias: {

rax: "react",
"rax-image": require.resolve("./components/rax-image"),
"rax-view": require.resolve("./components/rax-view"),
"rax-scrollview": require.resolve("./components/scroll-view"),
"@ali/ike-splayer": require.resolve("./components/ike-splayer"),
"@ali/ike-image": require.resolve("./components/ike-image")

},
},
對于命中了 alias 規(guī)則的元件,直接替換為我們自己精簡的組件。刪除里面的對 weex 的判斷,以及僅僅會在 weex 下運(yùn)行的代碼。

這一塊很簡單,沒什么好說的。

樣式使用的不同
這里是處理起來比較麻煩的地方,首先看看我們訴求:

樣式提取

外部引入的 css 文件,不再通過 css in js 的方式內(nèi)聯(lián)到每一個(gè)標(biāo)簽上,而是提取為常規(guī) css 文件,并配合 class 來實(shí)現(xiàn)常規(guī)的樣式布局方式

style 支持 Array

支持 rax 的特殊語法,即 style 可以傳入一個(gè) Array 類型

樣式提取
我們看一下 rax 下引用和使用外部 css 的方式:

import { Component, createElement, findDOMNode } from "rax";
import Text from "rax-text";
import View from "rax-view";
// 引入樣式文件
import styles from "./index.css";

class Kisnows extends Component {
constructor(props) {

super(props);
this.state = {
  count: 1
};

}

handleClick = () => {

this.setState({
  count: this.state.count + 1
});

};

render() {

const { count } = this.state;
const { name } = this.props;
return (
  // 使用樣式文件
  
    {name}
    
      怕什么真理無窮,進(jìn)一步有進(jìn)一步的好。
    
    點(diǎn)擊進(jìn)步:{count}
  
);

}
}

export default Kisnows;
這種使用方式和社區(qū)的 css modules 幾乎一致,那我們就可以用 css module 來解決這件事情。

思路是這樣的,對于賦值到 style 屬性上的外部 css 屬性(即從 css 文件里面引入,而非直接 js 里面定義的樣式),我們給它生成一個(gè)唯一字符串,然后組合起來放到 className 屬性上。對于 js 內(nèi)部定義的 css ,繼續(xù)作為內(nèi)聯(lián)樣式賦值到 style 屬性上。

在 webpack 的 css rule 里面引入 css-loader,然后開啟 css module 功能:

{

test: /.(css)$/,
use: [
  {
    loader: require.resolve("css-loader"),
    options: {
      importLoaders: 2,
      modules: {
        mode: "local",
        localIdentName: "[path][name]__[local]--[hash:base64:5]",
      },
      // sourceMap: !!DEV,
    },
  }
],

}
這樣配置了的話,通過 import styles from "./index.css" 引入的 styles 就會被 css-loader 處理,并且里面對類似 styles.wrap 的引入,就會變成 _3zyde4l1yATCOkgn-DBWEL 這樣的計(jì)算出來的唯一 id 字符串,然后賦值到 class 屬性上就好了。

至于如何把這個(gè) id 放到 className 上,我們放到后面一起說。
轉(zhuǎn)換后的效果如下:

import
目前還面臨兩個(gè)問題:

非標(biāo)準(zhǔn) css 文件處理
rax 下使用 weex 為了做移動(dòng)端適配,css 寫起來和普通的 css 有一點(diǎn)區(qū)別,也就是 rax 的 css 屬性都是沒有單位的。而我們提取出來的 css 肯定是需要單位才行的;
樣式優(yōu)先級問題
以前所有的樣式都是內(nèi)聯(lián)的,每個(gè)樣式的優(yōu)先級也都是一樣的,無論是直接寫在 js 里面的還是從 css 文件里面引入的,最終會在 js 內(nèi)部根據(jù)處理內(nèi)聯(lián) styles 的 Object.assign 入?yún)⒌捻樞驔Q定最終優(yōu)先級。但是我們提取出來以后就不一樣了,外鏈的 css 通過 class 作用與元素上,它的優(yōu)先級是低于內(nèi)聯(lián)樣式的,這樣就會導(dǎo)致
非標(biāo)準(zhǔn) css 文件處理
rax 下 css 文件樣例:

.wrap {
width: 750;
}

.name {
width: 750;
height: 124;
font-size: 24;
}
可以看到寬高之類屬性都是沒有單位的,這里需要處理成對應(yīng)的單位。處理起來也很簡單,postcss 是一個(gè)用來對 css 做后處理的工具,我們經(jīng)常使用的 autoprefix 就是 postcss 一個(gè)有名的插件。這里我們也借助它的插件能力來處理 css, 插件代碼如下:

const postcss = require("postcss");
const _ = require("lodash");
// 定義所有需要添加單位的屬性
const props = [
"width",
"height",
"padding",
"margin",
"margin-top",
"margin-bottom",
"top",
"bottom",
"right",
"left",
"border",
"box-shadow",
"border-radius",
"font-size"
];
/**

main function

*/
module.exports = postcss.plugin("realCss", function(options) {
return function(css) {

options = options || {};
const reg = /(d+)(?!%)/gm;
css.walkDecls(decl => {
  // 1. 遍歷所有 css 屬性,找到我們定義的需要添加單位的項(xiàng)
  if (
    _.find(props, props => {
      return decl.prop.includes(props);
    })
  ) {
    // 2. 簡單粗暴,直接添加 px
    decl.value = decl.value.replace(reg, a => a + "px");
  }
  // 3. 給所有屬性添加 !important ,提高優(yōu)先級
  decl.value += "!important";
});

};
});
相應(yīng)的我們在 webpack 的 css rule 里面加上 postcss 的配置:

{
loader: require.resolve("postcss-loader"),
options: {

plugins: [
  postcssPresetEnv(),
  // 我們自己的開發(fā) postcss 插件, realCss
  realCss(),
  post2Rem({ remUnit: 100 }),
],
sourceMap: !!DEV,

},
},
樣式優(yōu)先級問題
一開始還沒注意到這個(gè)問題,后來再測試的時(shí)候才發(fā)現(xiàn)有的樣式總是不生效,排查后才發(fā)現(xiàn)原來是因?yàn)闃邮絻?yōu)先級的問題。
舉例:

import { Component } from "rax";

class View extends Component {
render() {

let props = this.props;
let styleProps = {
  ...styles.initial,
  ...props.style
};
return 
;

}
}

const styles = {
initial: {

border: "0 solid black",
position: "relative",
boxSizing: "border-box",
display: "flex",
flexDirection: "column",
alignContent: "flex-start",
flexShrink: 0

}
};

export default View;
上面是 rax-view 的代碼,可以看到它有一個(gè)初始化的樣式,直接內(nèi)聯(lián)到了元素標(biāo)簽上。如果都是用 rax 開發(fā),所有樣式內(nèi)聯(lián),那么外部通過 props 傳下來的樣式 props.style 的優(yōu)先級是高于它自己的初始化樣式 styles.initial ,這樣是沒有問題的。

一旦我們把外部的 css 提取出去,那么這里的 props.styles 也基本就是我們要提取出來的東西,這里的一旦提取為外聯(lián) css 文件,那么它的優(yōu)先級就會永遠(yuǎn)低于 styles.initial , 這樣我們的樣式就會出錯(cuò)。

這里我沒有想到什么好的辦法來提升外聯(lián) css 的優(yōu)先級,所以直接粗暴的給每個(gè)外聯(lián) css 的屬性添加了 !important ,參考上面 realCss 的注釋 3 。方法搓了一點(diǎn),但是很能解決問題。而且不是硬編碼到 css 里,后續(xù)去掉也很簡單。

style 支持 Array
這里我們跟著上面提取外聯(lián) css 的屬性并放到 class 屬性上一起說。
我們要處理的 style 標(biāo)簽有如下四種:

1;
2;
3{name};
4;
對于這三種情況我們期望的結(jié)果應(yīng)該是這樣的:

保持不變,就是一個(gè)普通的 js 內(nèi)聯(lián)樣式


我們認(rèn)為 styles 都是從外部 css 文件引入的,所以需要替換 styles.wrap 為 class 字符串,并賦值到 class 屬性上

const cls = "className";
return ;
這種情況是前面兩種的組合,對于內(nèi)聯(lián)樣式,繼續(xù)內(nèi)聯(lián)到 style 屬性上,對于外部引用的 styles ,替換為 class 字符串放到 className 上

const cls = "className";
const style = Object.assign({}, { color: "red" }, { fontSize: 24 });
return ;
需要向上查找當(dāng)前變量的類型,如果是 Array,那就參照上一條處理,否則認(rèn)為是內(nèi)聯(lián)樣式,按照 1 處理。

有了期望的結(jié)果,那解決問題的思路也就比較容易了。我們要做的就是對現(xiàn)有 style 標(biāo)簽的屬性做判斷:

如果是 object ,對應(yīng) ast 解析后的 ObjectExpression ,那么直接當(dāng)作內(nèi)聯(lián)樣式處理
如果是一個(gè)從 styles (為了簡化判斷,我們默認(rèn) styles 是從外部 css 引入的)上讀值,對應(yīng) ast 解析后的 MemberExpression, 認(rèn)為是從外部 css 引入的樣式
如果是一個(gè) Array ,對應(yīng) ast 解析后的 ArrayExpression ,把里面的東西遍歷一邊,找到要轉(zhuǎn)換成 class 字符串的需要內(nèi)聯(lián)的樣式,經(jīng)過處理后,再放到當(dāng)前元素的 className 屬性和 style 屬性上
還有一種是值是另外一個(gè)定義的變量,也就是 ast 解析后的 Identifier ,這種需要判斷里面的值是不是 Array ,是的話按照上一條處理,否則認(rèn)為是內(nèi)聯(lián)樣式
對上面三種情況的操作抽象一下:

找到有 style 元素的標(biāo)簽,然后判斷 style 的值,提取處理里面要內(nèi)聯(lián)的 css 和要轉(zhuǎn)換為 class 的樣式,
根據(jù)上一步的結(jié)果重建 style 屬性,并添加 className 屬性
打開 https://astexplorer.net/ 看一下,每個(gè)元素對應(yīng)的都是一個(gè) JSXOpeningElement ,我們要做的就是遍歷 JSXOpeningElement ,然后對每個(gè)標(biāo)簽|組件做處理。

import
具體實(shí)現(xiàn)邏輯:

第一步:找內(nèi)聯(lián) css 和需要轉(zhuǎn)換的 class

JSXOpeningElement: {
enter(path) {

const node = path.node;
// 用來存放找到的內(nèi)聯(lián) css
const styles = [];
// 用來存放被轉(zhuǎn)換的 class
const classNames = [];
let newStyleAttr = null;
let newClassNameAttr = null;
let styleAttrPath = null;

path.traverse({
  JSXAttribute(path) {
    if (path.node.name.name !== "style") return;
    styleAttrPath = path;
    path.traverse({
      /**
       * 從離元素最近的一個(gè)方法往下找,判斷 style 的值是否是一個(gè) Array,
       * 僅限查找直接的變量,而非從對象上讀取的。
       * eg: style={[list, obj.arr]} ,則只查找 list 而不管 obj.arr
       */
      Identifier(identifyPath) {
        const name = identifyPath.node.name;
        const parent = identifyPath.parent;
        if (t.isMemberExpression(parent)) return false;
        let isArray = false;
        // 從當(dāng)前 path 向上查找最近的一個(gè)方法
        const par = identifyPath.findParent(p => {
          if (t.isClassMethod(p) || t.isFunction(p)) {
            // 從 render  方法里面往下找當(dāng)前變量的定義,
            p.traverse({
              VariableDeclarator(path) {
                if (
                  t.isArrayExpression(path.node.init) &&
                  path.node.id.name === name
                ) {
                  isArray = true;
                }
              }
            });
          }
        });

        if (isArray) {
          // TODO: 如果是 Array ,則重新走一下后面的 ArrayExpression 的處理
          // 創(chuàng)建當(dāng)前作用域下的唯一變量
          const arrayStyle = identifyPath.scope.generateUidIdentifier(
            "arrayStyle"
          );
          // 生成新的變量定義語句,
          // 如果是 Array ,那么認(rèn)為里面每個(gè)元素都是內(nèi)聯(lián)樣式,通過 Object.assign 把它們組合到一起
          const preformArrayStyle = template.ast(`
            const ${arrayStyle.name} = {}
            ${name}.forEach(sty => {
              if (typeof sty === "object") {
                Object.assign(${arrayStyle.name}, sty)
              }
            })
          `);
          const jsxParent = identifyPath.findParent(p => {
            if (
              t.isReturnStatement(p) ||
              t.isVariableDeclaration(p)
            ) {
              return true;
            }
          });
          // 在最近的 return 語句上插入生成的語句
          jsxParent.insertBefore(preformArrayStyle);
          // 把當(dāng)前 style 的值賦值為我們新建的變量名 arrayStyle
          identifyPath.node.name = arrayStyle.name;
        }
      },
      /**
       * 如果是變量上讀取的屬性,則認(rèn)為是從外部 css 引入的樣式。通過 css-loader 的處理后,
       * 引入的值已經(jīng)變成了一個(gè)包含所有 class 的 object,我們直接把它替換為 style 就好了
       * */
      MemberExpression(path) {
          // function replaceStyle(path) {
          //   if (!path.parentPath.parent.name) return;
          //   path.parentPath.parent.name.name = "className";
          // }
        replaceStyle(path);
      },
      /**
       *  如果是 Array ,那么判斷里面的值,規(guī)則按照上面兩種處理方式處理。
       * */
      ArrayExpression(arrayExpressionPath) {
        const eles = arrayExpressionPath.node.elements;
        // 遍歷 Array 里面的元素
        eles.forEach(e => {
          // MemberExpression 認(rèn)為是處理后的 class string
          if (t.isMemberExpression(e)) {
            classNames.push(e);
          } else if (t.isObjectExpression(e)) {
            // 如果是 Object 表達(dá)式,認(rèn)為是內(nèi)聯(lián)樣式
            styles.push(e);
          } else if (t.isIdentifier(e)) {
            // 如果是自定義變量,粗暴的認(rèn)為是內(nèi)聯(lián)樣式
            styles.push(e);
          } else if (t.isLogicalExpression(e)) {
            // 由于不好判斷最終返回的值類型, 所以直接假定返回的 string ,當(dāng)作 className處理
            classNames.push(e);
          }
        });
      }
    });
  }
});

}
這樣我們就可以拿到對應(yīng) styles 和 classNames ,接下來就是用它們來重建我們的 style 和 className 屬性,看代碼:

if (!styles.length && !classNames.length) return;
/**

NOTE: 重建樣式屬性

刪除 style 屬性節(jié)點(diǎn)

用 styles 創(chuàng)建新的 style 節(jié)點(diǎn)

用 classNames 創(chuàng)建 className 節(jié)點(diǎn)

*/
// 嘗試獲取最近的一個(gè) render 方法
const renderPath = getRenderPath(path);
// 獲取最近的一個(gè) return 方法
let returnPath = getReturnPath(path);

// NOTE: 生成唯一 id ,并插入合并 styles 的代碼,
styleAttrPath.remove();
if (styles.length) {
if (!renderPath) return false;
// 為 style 值創(chuàng)建當(dāng)前作用域唯一變量名
const styleUid = path.scope.generateUidIdentifier("style_UID");

function buildStyleScript(styleUidName, styles) {

const test = t.callExpression(
  t.memberExpression(t.identifier("Object"), t.identifier("assign")),
  styles
);
const newScript = t.variableDeclaration("const", [
  t.variableDeclarator(styleUidName, test)
]);
return newScript;

}

const newScript = buildStyleScript(styleUid, styles);
// 在 return 語句前添加當(dāng)前 style_UID 的變量定義
returnPath.insertBefore(newScript);
newStyleAttr = t.jsxAttribute(

t.jsxIdentifier("style"),
getAttributeValue({ value: styleUid.name, literal: true })

);
path.node.attributes.push(newStyleAttr);
}
if (classNames.length) {
// 構(gòu)建并插入 className 字段
if (!renderPath) return;
// 為 className 創(chuàng)建當(dāng)前作用域唯一變量名
const classNameUid = path.scope.generateUidIdentifier("className_UID");
function buildClassNameScript(classNameUid, nodes) {

// DONE: 構(gòu)建一個(gè) List ,用來創(chuàng)建 className 字符串
const array = t.arrayExpression(nodes);
const call = t.callExpression(
  t.memberExpression(array, t.identifier("join")),
  [t.stringLiteral(" ")]
);
const newScript = t.variableDeclaration("const", [
  t.variableDeclarator(classNameUid, call)
]);
return newScript;

}

const newScript = buildClassNameScript(classNameUid, classNames);
// 在 return 前插入當(dāng)前 className_UID 的變量定義
returnPath && returnPath.insertBefore(newScript);

// 構(gòu)建 className 屬性節(jié)點(diǎn)
newClassNameAttr = t.jsxAttribute(

t.jsxIdentifier("className"),
getAttributeValue({ value: classNameUid.name, literal: true })

);
// 給當(dāng)前 jsx 標(biāo)簽添加 className 屬性節(jié)點(diǎn)
path.node.attributes.push(newClassNameAttr);
}
這樣處理下來,整個(gè) rax 組件就可以編譯到 react 了,涉及到的工具有 webpack ,babel,完整的示例代碼請參考:

總結(jié)
上面在對 ast 語法樹的轉(zhuǎn)換上,有很多假定,比如我們認(rèn)定 styles.xxx 中的 styles 就是從外部 css 引入的變量,在此基礎(chǔ)上做了后面的提取為 class 的事情。
還有一些比較粗暴的做法,畢竟如果要考慮所有情況的話,成本就有點(diǎn)高了,不過目前已經(jīng)能滿足我們的需求了。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/106856.html

相關(guān)文章

  • 什么是 Rax,以及 Rax 的特點(diǎn)

    摘要:與是一種標(biāo)準(zhǔn),是對該標(biāo)準(zhǔn)的一個(gè)實(shí)現(xiàn)。因此支持了返回多個(gè)同級節(jié)點(diǎn)的功能,如這一特性可以有效減少頁面的嵌套層級,從而減少應(yīng)用因嵌套層級過多而出現(xiàn)的問題。未來這是口號,亦是目標(biāo)。 showImg(https://segmentfault.com/img/bVOcyp?w=110&h=110); Rax is a universal JavaScript library with a larg...

    William_Sang 評論0 收藏0

發(fā)表評論

0條評論

最新活動(dòng)
閱讀需要支付1元查看
<