webpack适配-Taro 3.2 适配 React Native 风格 shady

指导

Taro 3.2正式版将于3月底发布,技术讲解文章也在紧锣密鼓地进行中。 Taro3.2 适配 React Native 系列文章的第一部分提到了运行时架构的解读。 本文将为您带来风格改编的内幕。

背景

从Taro 3开始,58同城成为Taro的战略合作伙伴,负责Taro 3的React Native部分的开发和推广。我们总结了京东Taro同学这些年的适配经验,以及技术内部使用的React Native的积累。 为了更好地提升开发体验,我们提出了一种新的架构[0]。

新的架构设计下,之前的样式处理方案需要重新设计和插入。 在接入的过程中,需要考虑React-Native风格管理和风格的差异,框架提供了人性化的兼容方案,以便在工程开发中更好的组织代码。

React-Native的样式支持基本上实现了CSS的子集,但属性名称并不完全相同。 最大的区别是不支持级联样式表,并且无法使用类读取静态样式webpack适配,因此很难适应Web。

适应问题

在开始接触样式系统之前,我们先来看一段代码,常见的React Native代码设置样式:

import React from "react";import { StyleSheet, Text, View } from "react-native";
const App = () => ( React Native );
const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: "#eaeaea" }, title: { fontSize: 30, fontWeight: "bold" }});
export default App;

常见Taro其他终端代码设置样式:

// app.jsimport React from "react";import { Text, View } from "@tarojs/components";import "./app.css";
const App = () => ( React Native );
export default App;
// app.css.container { flex: 1; background-color: "#eaeaea";}.title { font-size: 30; font-weight: "bold";}

通过分析里面的代码,Taro 适配 React Native 代码有如下和衍生的问题:

1、React Native代码(样式代码)与其他平台上样式管理的区别

React Native 风格仅支持声明式风格

其他端,比如小程序端webpack适配,可以通过类读取导出的样式,同时使用声明式样式和布局

全局安装webpack_webpack_webpack适配

2.如何将通用样式文件转换为对象

3.如何传递组件样式

React Native使用样式来传递,其他端使用类。 理论上来说,是受到书写风格的制约,这样三端才能更好的适配,CSS In JS 风格的方案更适合。

React Native支持的样式有限,如何处理不支持的样式

4、如何灵活处理平台类型的特殊逻辑和特殊风格

Taro处理过跨端文件,但仅限于脚本文件。 我们希望样式文件也可以跨端,希望ios和android的差异文件能够匹配。 例如Shadow样式只能在IOS上生效,Android则需要使用elevation来代替。 当然可以重画,但有时工程需要可以清晰区分两端。

总体设计

流程图

风格适配设计流程图:

下面是该过程的解释,并有相应的流程图表示。

核心流程:

样式代码作为对象处理

风格校准

标签属性className被处理成style

展开补充流程:

4.多种预编译语言的适配

5.更灵活的跨平台工程适配

设计实施

样式代码作为对象处理

如果我们需要样式文件在 React Native 中可用,我们首先需要将样式 CSS 处理为样式对象。 这里的第一步是将 CSS 解析为 AST 树 [1]:

body {
background: #eee;
color: #888;
}

遍历解析后的 AST 规则(选择器),然后遍历上面的声明(样式属性),使用 css-to-react-native[2] 将 CSS 属性转换为 React Native 样式属性,并在选择器中命名对象设置转换后的属性值和属性名称。 最后,使用一个大的样式对象来存储选择器命名的对象。

将样式处理为 JS 对象 [3]:

这一步在源码实现中将css-to-react-native和CSS解析器封装到taro-css-to-react-native NPM包中。

最后导入转换后的样式对象:

webpack_webpack适配_全局安装webpack

import { StyleSheet } from 'react-native'
const cssObject = {/**/}
export default StyleSheet.create(cssObject)

标签属性className被处理成style

上面提到的文件导出处理和多文件合并都是通过 Babel 插件实现的,而 className 转成 style 的处理也是通过 AST 修改 jsxElement 的属性。 以下是插件实现属性转换的核心逻辑:

上面就是如何将通常的CSS语法文件处理成可以被React Native标签的style属性接收的样式对象。 实际开发生产中,更推荐使用样式预处理器。 毕竟,优秀的句子糖可以提高劳动人民的生产力。

多种预编译语言的适配

格子风格

格子风格

设计过程中,所有样式文件都经过预处理语言PostCSS处理,从而可以集中处理一些常见的事务,如stylelint、条件编译、单元处理等。 stylelint检测用于校准样式书写,使用stylelint插件,通过stylelint-config-taro-rn[9]配置规则约束不支持的样式书写,例如校准组合选择器。

验证样式对象

React Native支持的样式是有限的,编写一些不支持的样式会导致应用程序报错或者崩溃,所以我们需要一个函数来校准代码并在控制台复制样式错误日志。

当编写不支持的样式属性时:

在控制台上提示用户错误信息:

全局安装webpack_webpack适配_webpack

更灵活的跨平台工程适配

总结

通过使用之前的一些技术手段,Taro 在开发 React Native 的过程中能够有比较好的体验。 但在React Native样式属性作为样式管理的限制下,对其他端样式的适配仍然存在限制。

1.未来优化的方向以及仍然存在的样式限制——选择器约束

在 JSX 层,只有 className 被转换为样式,这个限制导致选择器上只能使用类选择器。

在CSS转换为对象的层面,类选择器直接转换为对象,并且不支持组合选择器,这就限制了组合选择器的使用。

在预处理语言的嵌套写法中,不能使用可编译为组合选择器的写法,但可以使用可编译为BEM[11]的嵌套写法。

2.关于组合选择器的一点思考

我。 原子CSS

Atomic CSS[12]是Facebook今年5月提出的样式管理解决方案。 官方表示,使用 Atomic CSS 使首页 CSS 代码量减少了 80%。 感兴趣的朋友可以访问Facebook主页查看。

原子CSS意味着每个样式属性设置都对应一个类选择器来控制,避免重复的样式代码。 例如,如果你使用公共样式,只想使用它的部分样式,那么你会降低一级(后代选择器)来重绘你不想使用的样式,这在一定程度上减少了代码量。 如果项目的样式使用Atomic CSS,那么组合选择器的支持就变得不必要了。

二. JS 中的 CSS

JS[13]中的CSS使用语法糖来定义JS中的样式。 本质上,样式是内联编写的。 这与React Native的样式管理理念是一致的,也是JS爱好者中CSS强烈推荐的。 不过官方小程序中提到,style接收动态样式,运行时会解析,会影响渲染速率。

参考:

[0]

[1]

[2]

[3]

[4]%2Freact-native/packages/babel-plugin-transform-react-jsx-to-rn-stylesheet/tests/index.spec.js

[5]

[6]

[7]

[8]

[9]

[10]

[11]

[12]

[13]

关于作者:

王新建:房地产事业群--新房后端技术部

汇总的优点

在最新的Vue3版本中,也使用rollup作为打包工具。 与webpack相比,rollup要轻量很多。 不用摇树就能填满吞咽,是个不错的选择。 最大的用处就是打包制作库文件,比如sdk.js之类的。 虽然webpack也可以做到webpack打包优化,但是webpack比较重,而且打包后的文件有一些webpack的内部代码,比如__webpack__require这样的函数定义,给人一种不干净的感觉。 不过rollup创建的包非常干净,没有任何其他冗余代码。

如何使用

1.安装汇总

npm i rollup -g

二、几种使用方法

1.命令行操作

rollup src/main.js -o rel/bundle.js  -f cjs //将main.js(es5)编译输出至bundle.js(commonjs)

2、配置文件方式

//新建一个rollup.config.js 
export default {
  input: 'src/main.js',  // 入口文件
  output: {  // 输出 options
    file: 'bundle.js',  // 输出文件名
    format: 'cjs'       // 输出格式
  }
}
//执行
rollup -c   //当你的配置文件另有其名(dev),执行 rollup -c rollup.config.dev.js

3.模块模式(方便与gulp等其他工具配合)

//rollup.config.js 
var rollup = require( 'rollup' );
var babel = require('rollup-plugin-babel');
rollup.rollup({
    entry: 'src/main.js',
    plugins: [ babel() ]
}).then( function ( bundle ) {
    bundle.write({
        format: 'umd',
        moduleName: 'main', //umd或iife模式下,若入口文件含 export,必须加上该属性
        dest: 'rel/bundle.js'
    });
});
//命令行执行
node rollup.config.js

相关配置说明

1. 输入

入口文件地址

二、输出

output:{
    file:'bundle.js', // 输出文件
    format: 'cjs,  //  五种输出格式:amd /  es6 / iife / umd / cjs
    name:'A',  //当format为iife和umd时必须提供,将作为全局变量挂在window(浏览器环境)下:window.A=...
    sourcemap:true  //生成bundle.map.js文件,方便调试
}

三、插件

最常用的是babel插件。 比较烦人的是,与 webpack 不同,babel 的预设可以直接写在配置文件中,但你还是要独立写“src/.babelrc”(注意,我们可以写在 src 下,而且不一定要写在 src 下)放在项目根目录下),并确保安装插件:npm i rollup-plugin-babel

//rollup.config.js 
import babel from 'rollup-plugin-babel';
export default {
  input: 'src/main.js',
  output: {
    file: 'bundle.js',
    format: 'cjs'
  },
  plugins: [ // 增加 plugins
    babel({
      exclude: 'node_modules/**' // 不对node_modules进行编译
    })
  ]
}
//.babelrc
{
  "presets": [
    ["@babel/env", {"modules": false}]
  ]
}

四、外部

external: ['react', 'redux'],// 告诉rollup,不打包react,redux;将其视为外部依赖

五、全球

globals: {
  react: 'React', // 这跟external 是配套使用的,指明global.React即是外部依赖react
  redux: 'Redux'
}

主要依赖

rollup 很难识别node_modules 中的包。 您需要安装插件 rollup-plugin-node-resolvewebpack打包优化,然后在插件中使用它。

node_modules 中的大部分包都是 commonjs 格式。 要在 rollup 中使用它们,必须首先将它们转换为 ES6 句型。 为此,需要安装插件 rollup-plugin-commonjs。

附上react-redux开源项目的rollup配置文件

import nodeResolve from 'rollup-plugin-node-resolve' // 帮助寻找node_modules里的包
import babel from 'rollup-plugin-babel'  // rollup 的 babel 插件,ES6转ES5
import replace from 'rollup-plugin-replace' // 替换待打包文件里的一些变量,如 process在浏览器端是不存在的,需要被替换
import commonjs from 'rollup-plugin-commonjs' // 将非ES6语法的包转为ES6可用
import uglify from 'rollup-plugin-uglify'  // 压缩包
const env = process.env.NODE_ENV
const config = {
  input: 'src/index.js',
  external: ['react', 'redux'],  // 告诉rollup,不打包react,redux;将其视为外部依赖
  output: { 
    format: 'umd', // 输出 UMD格式,各种模块规范通用
    name: 'ReactRedux', // 打包后的全局变量,如浏览器端 window.ReactRedux 
    globals: {
      react: 'React',   // 这跟external 是配套使用的,指明global.React即是外部依赖react
      redux: 'Redux'
    }
  },
  plugins: [
    nodeResolve(),
    babel({
      exclude: '**/node_modules/**'
    }),
    replace({
      'process.env.NODE_ENV': JSON.stringify(env)
    }),
    commonjs()
  ]
}
if (env === 'production') {
  config.plugins.push(
    uglify({
      compress: {
        pure_getters: true,
        unsafe: true,
        unsafe_comps: true,
        warnings: false
      }
    })
  )
}
export default config