国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

如何把 alibaba Rax 組件轉換到 React 下

Hydrogen / 3309人閱讀

摘要:如何轉換知道了二者的不同,那么如何轉換我們也就有方向了。因為下每個元件本身就是一個普通的組件,我們可以通過直接把他們當作其他組件轉換為的代碼來使用。至于如何把這個放到上,我們放到后面一起說。

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

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

當然,有人會說那是因為你對 weex 的 api 不了解,也對,一直以來對與這種自己搞一套非標準體系來實現各種魔法功能的東西都不怎么感興趣 但是如果了解它的成本大于了它帶來的收益,那么對我們來說,就沒有必要做這件事情。

weex 相對于 H5 ,最大的優點在于交互性能上要更好一點。

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

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

期望的目標
對于一個 rax 模塊,我們期望通過編譯后:

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

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}
    
      怕什么真理無窮,進一步有進一步的好。
    
    點擊進步:{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}

怕什么真理無窮,進一步有進一步的好。
點擊進步:{count}
);

}
}

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

引用的框架不同

這個當然是廢話,
rax 模塊從 rax 上引入 Component, createElement 等組件的, react 模塊從 react 上引入。

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

使用的基礎元件不同

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

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

樣式使用的不同

rax 下所有的樣式文件都是 css in js ,即使后面通過構建等支持了引用外部 css 文件的樣式,但依然還是 css in js ,所有的樣式都是內聯的。

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

而一旦傳入的是個 Array ,js 又是動態語言,所以無法在編譯時判斷里面元素的類型,就不好判斷里面的元素哪些是要轉換為 class ,哪些是直接內聯的樣式了。我們轉換上很多的精力也是花在了這里。

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

對代碼進行轉換,現在最常見的方法就是基于 babel 的插件來進行,首先把代碼轉換成 ast 語法樹,然后在此基礎上對 ast 進行轉換,最后把 ast 轉換成我們需要的代碼。

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

介紹幾個學習中參考的文檔和工具:

babel-handbook
幾乎涵蓋開發 babel 插件需要的一切。
AST Explorer
一個在線進行語法解析的網站,可以實時驗證你的想法。
由于 babel-handbook 已經對如何開發 babel 插件講解的很詳細了,對一些基本概念本文就不再贅述,默認往下看的讀者都已經有了這些知識基礎。

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

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

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

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

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

具體到操作上,就是我們在 visitor 里面添加對 ImportDeclaration 類型節點的遍歷:

visitor: {
ImportDeclaration(path) {

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

}
}
然后區分哪些模塊是要從 react 里面引用的,哪些是需要從 react-dom 里面引用的,再對 ImportSpecifier 節點進行遍歷,找到里面符合我們符合條件的模塊,方便后面生成新的 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(
            `當前方法 ${methodName} 沒有進行配置,直接從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(
            `當前方法 ${methodName} 沒有進行配置,直接從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";
    `);
// 插入到當前 path 前面
path.insertBefore(importReactTemplate);
path.insertBefore(importReactDOMTemplate);
// 刪除當前 path ,也就是 rax 的 import 語句
path.remove();

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

import
使用的基礎元件不同
對于基礎元件這一塊,我們可以不做任何處理。因為 rax 下每個元件本身就是一個普通的 rax 組件,我們可以通過 babel 直接把他們當作其他 rax 組件轉換為 react 的代碼來使用。

但是我們轉換后的代碼只需要在 Web 上跑,而如果你看過基礎元件比如用 rax-text 舉例的代碼:rax-text 。
里面有很多代碼是僅在 weex 下運行的,我們根本不需要,所以可以通過一些粗暴的方法來精簡這些元件。直接使用 Webpack 的 alias ,來把對 rax-text 等基礎元件的引用直接指定到我們的自己的一個組件下,比如我們自己就把以下元件加入到了 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 規則的元件,直接替換為我們自己精簡的組件。刪除里面的對 weex 的判斷,以及僅僅會在 weex 下運行的代碼。

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

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

樣式提取

外部引入的 css 文件,不再通過 css in js 的方式內聯到每一個標簽上,而是提取為常規 css 文件,并配合 class 來實現常規的樣式布局方式

style 支持 Array

支持 rax 的特殊語法,即 style 可以傳入一個 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}
    
      怕什么真理無窮,進一步有進一步的好。
    
    點擊進步:{count}
  
);

}
}

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

思路是這樣的,對于賦值到 style 屬性上的外部 css 屬性(即從 css 文件里面引入,而非直接 js 里面定義的樣式),我們給它生成一個唯一字符串,然后組合起來放到 className 屬性上。對于 js 內部定義的 css ,繼續作為內聯樣式賦值到 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 這樣的計算出來的唯一 id 字符串,然后賦值到 class 屬性上就好了。

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

import
目前還面臨兩個問題:

非標準 css 文件處理
rax 下使用 weex 為了做移動端適配,css 寫起來和普通的 css 有一點區別,也就是 rax 的 css 屬性都是沒有單位的。而我們提取出來的 css 肯定是需要單位才行的;
樣式優先級問題
以前所有的樣式都是內聯的,每個樣式的優先級也都是一樣的,無論是直接寫在 js 里面的還是從 css 文件里面引入的,最終會在 js 內部根據處理內聯 styles 的 Object.assign 入參的順序決定最終優先級。但是我們提取出來以后就不一樣了,外鏈的 css 通過 class 作用與元素上,它的優先級是低于內聯樣式的,這樣就會導致
非標準 css 文件處理
rax 下 css 文件樣例:

.wrap {
width: 750;
}

.name {
width: 750;
height: 124;
font-size: 24;
}
可以看到寬高之類屬性都是沒有單位的,這里需要處理成對應的單位。處理起來也很簡單,postcss 是一個用來對 css 做后處理的工具,我們經常使用的 autoprefix 就是 postcss 一個有名的插件。這里我們也借助它的插件能力來處理 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 屬性,找到我們定義的需要添加單位的項
  if (
    _.find(props, props => {
      return decl.prop.includes(props);
    })
  ) {
    // 2. 簡單粗暴,直接添加 px
    decl.value = decl.value.replace(reg, a => a + "px");
  }
  // 3. 給所有屬性添加 !important ,提高優先級
  decl.value += "!important";
});

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

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

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

},
},
樣式優先級問題
一開始還沒注意到這個問題,后來再測試的時候才發現有的樣式總是不生效,排查后才發現原來是因為樣式優先級的問題。
舉例:

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 的代碼,可以看到它有一個初始化的樣式,直接內聯到了元素標簽上。如果都是用 rax 開發,所有樣式內聯,那么外部通過 props 傳下來的樣式 props.style 的優先級是高于它自己的初始化樣式 styles.initial ,這樣是沒有問題的。

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

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

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

1;
2;
3{name};
4;
對于這三種情況我們期望的結果應該是這樣的:

保持不變,就是一個普通的 js 內聯樣式


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

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

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

有了期望的結果,那解決問題的思路也就比較容易了。我們要做的就是對現有 style 標簽的屬性做判斷:

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

找到有 style 元素的標簽,然后判斷 style 的值,提取處理里面要內聯的 css 和要轉換為 class 的樣式,
根據上一步的結果重建 style 屬性,并添加 className 屬性
打開 https://astexplorer.net/ 看一下,每個元素對應的都是一個 JSXOpeningElement ,我們要做的就是遍歷 JSXOpeningElement ,然后對每個標簽|組件做處理。

import
具體實現邏輯:

第一步:找內聯 css 和需要轉換的 class

JSXOpeningElement: {
enter(path) {

const node = path.node;
// 用來存放找到的內聯 css
const styles = [];
// 用來存放被轉換的 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({
      /**
       * 從離元素最近的一個方法往下找,判斷 style 的值是否是一個 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;
        // 從當前 path 向上查找最近的一個方法
        const par = identifyPath.findParent(p => {
          if (t.isClassMethod(p) || t.isFunction(p)) {
            // 從 render  方法里面往下找當前變量的定義,
            p.traverse({
              VariableDeclarator(path) {
                if (
                  t.isArrayExpression(path.node.init) &&
                  path.node.id.name === name
                ) {
                  isArray = true;
                }
              }
            });
          }
        });

        if (isArray) {
          // TODO: 如果是 Array ,則重新走一下后面的 ArrayExpression 的處理
          // 創建當前作用域下的唯一變量
          const arrayStyle = identifyPath.scope.generateUidIdentifier(
            "arrayStyle"
          );
          // 生成新的變量定義語句,
          // 如果是 Array ,那么認為里面每個元素都是內聯樣式,通過 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);
          // 把當前 style 的值賦值為我們新建的變量名 arrayStyle
          identifyPath.node.name = arrayStyle.name;
        }
      },
      /**
       * 如果是變量上讀取的屬性,則認為是從外部 css 引入的樣式。通過 css-loader 的處理后,
       * 引入的值已經變成了一個包含所有 class 的 object,我們直接把它替換為 style 就好了
       * */
      MemberExpression(path) {
          // function replaceStyle(path) {
          //   if (!path.parentPath.parent.name) return;
          //   path.parentPath.parent.name.name = "className";
          // }
        replaceStyle(path);
      },
      /**
       *  如果是 Array ,那么判斷里面的值,規則按照上面兩種處理方式處理。
       * */
      ArrayExpression(arrayExpressionPath) {
        const eles = arrayExpressionPath.node.elements;
        // 遍歷 Array 里面的元素
        eles.forEach(e => {
          // MemberExpression 認為是處理后的 class string
          if (t.isMemberExpression(e)) {
            classNames.push(e);
          } else if (t.isObjectExpression(e)) {
            // 如果是 Object 表達式,認為是內聯樣式
            styles.push(e);
          } else if (t.isIdentifier(e)) {
            // 如果是自定義變量,粗暴的認為是內聯樣式
            styles.push(e);
          } else if (t.isLogicalExpression(e)) {
            // 由于不好判斷最終返回的值類型, 所以直接假定返回的 string ,當作 className處理
            classNames.push(e);
          }
        });
      }
    });
  }
});

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

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

NOTE: 重建樣式屬性

刪除 style 屬性節點

用 styles 創建新的 style 節點

用 classNames 創建 className 節點

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

// NOTE: 生成唯一 id ,并插入合并 styles 的代碼,
styleAttrPath.remove();
if (styles.length) {
if (!renderPath) return false;
// 為 style 值創建當前作用域唯一變量名
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 語句前添加當前 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) {
// 構建并插入 className 字段
if (!renderPath) return;
// 為 className 創建當前作用域唯一變量名
const classNameUid = path.scope.generateUidIdentifier("className_UID");
function buildClassNameScript(classNameUid, nodes) {

// DONE: 構建一個 List ,用來創建 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 前插入當前 className_UID 的變量定義
returnPath && returnPath.insertBefore(newScript);

// 構建 className 屬性節點
newClassNameAttr = t.jsxAttribute(

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

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

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

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/106856.html

相關文章

  • 什么是 Rax,以及 Rax 的特點

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

    William_Sang 評論0 收藏0

發表評論

0條評論

最新活動
閱讀需要支付1元查看
<