不可能的是

不可能的是 查看完整档案

北京编辑武汉工程大学  |  软件工程 编辑@nbsp;  |  前端开发 编辑 1234.github.io/ 编辑
编辑
stay hungry&&foolish

个人动态

不可能的是 收藏了文章 · 10月17日

如何画出一张合格的技术架构图?

阿里妹导读:技术传播的价值,不仅仅体现在通过商业化产品和开源项目来缩短我们构建应用的路径,加速业务的上线速率,也体现在优秀工程师在工作效率提升、产品性能优化和用户体验改善等经验方面的分享,以提高我们的专业能力。

接下来,阿里巴巴技术专家三画,将分享自己和团队在画好架构图方面的理念和经验,希望对你有所帮助。

当我们想用一张或几张图来描述我们的系统时,是不是经常遇到以下情况:

  • 对着画布无从下手、删了又来?
  • 如何用一张图描述我的系统,并且让产品、运营、开发都能看明白?
  • 画了一半的图还不清楚受众是谁?
  • 画出来的图到底是产品图功能图还是技术图又或是大杂烩?
  • 图上的框框有点少是不是要找点儿框框加进来?
  • 布局怎么画都不满意……

如果有同样的困惑,本文将介绍一种画图的方法论,来让架构图更清晰。

先厘清一些基础概念

1、什么是架构?

架构就是对系统中的实体以及实体之间的关系所进行的抽象描述,是一系列的决策。

架构是结构和愿景。

系统架构是概念的体现,是对物/信息的功能与形式元素之间的对应情况所做的分配,是对元素之间的关系以及元素同周边环境之间的关系所做的定义。

做好架构是个复杂的任务,也是个很大的话题,本篇就不做深入了。有了架构之后,就需要让干系人理解、遵循相关决策。

2、什么是架构图?

系统架构图是为了抽象地表示软件系统的整体轮廓和各个组件之间的相互关系和约束边界,以及软件系统的物理部署和软件系统的演进方向的整体视图。

3、架构图的作用

一图胜千言。要让干系人理解、遵循架构决策,就需要把架构信息传递出去。架构图就是一个很好的载体。那么,画架构图是为了:

  • 解决沟通障碍
  • 达成共识
  • 减少歧义

4、架构图分类

搜集了很多资料,分类有很多,有一种比较流行的是4+1视图,分别为场景视图、逻辑视图、物理视图、处理流程视图和开发视图。

场景视图

场景视图用于描述系统的参与者与功能用例间的关系,反映系统的最终需求和交互设计,通常由用例图表示。

逻辑视图

逻辑视图用于描述系统软件功能拆解后的组件关系,组件约束和边界,反映系统整体组成与系统如何构建的过程,通常由UML的组件图和类图来表示。

物理视图

物理视图用于描述系统软件到物理硬件的映射关系,反映出系统的组件是如何部署到一组可计算机器节点上,用于指导软件系统的部署实施过程。

处理流程视图

处理流程视图用于描述系统软件组件之间的通信时序,数据的输入输出,反映系统的功能流程与数据流程,通常由时序图和流程图表示。

开发视图

开发视图用于描述系统的模块划分和组成,以及细化到内部包的组成设计,服务于开发人员,反映系统开发实施过程。

以上 5 种架构视图从不同角度表示一个软件系统的不同特征,组合到一起作为架构蓝图描述系统架构。

怎样的架构图是好的架构图

上面的分类是前人的经验总结,图也是从网上摘来的,那么这些图画的好不好呢?是不是我们要依葫芦画瓢去画这样一些图?

先不去管这些图好不好,我们通过对这些图的分类以及作用,思考了一下,总结下来,我们认为,在画出一个好的架构图之前, 首先应该要明确其受众,再想清楚要给他们传递什么信息 ,所以,不要为了画一个物理视图去画物理视图,为了画一个逻辑视图去画逻辑视图,而应该根据受众的不同,传递的信息的不同,用图准确地表达出来,最后的图可能就是在这样一些分类里。那么,画出的图好不好的一个直接标准就是:受众有没有准确接收到想传递的信息。

明确这两点之后,从受众角度来说,一个好的架构图是不需要解释的,它应该是自描述的,并且要具备一致性和足够的准确性,能够与代码相呼应。

画架构图遇到的常见问题

1、方框代表什么?

为什么适用方框而不是圆形,它有什么特殊的含义吗?随意使用方框或者其它形状可能会引起混淆。

2、虚线、实线什么意思?箭头什么意思?颜色什么意思?

随意使用线条或者箭头可能会引起误会。

3、运行时与编译时冲突?层级冲突?

架构是一项复杂的工作,只使用单个图表来表示架构很容易造成莫名其妙的语义混乱。

本文推荐的画图方法

C4 模型使用容器(应用程序、数据存储、微服务等)、组件和代码来描述一个软件系统的静态结构。这几种图比较容易画,也给出了画图要点,但最关键的是,我们认为,它明确指出了每种图可能的受众以及意义。

下面的案例来自C4官网,然后加上了一些我们的理解,来看看如何更好的表达软件架构

1、语境图(System Context Diagram)

这是一个想象的待建设的互联网银行系统,它使用外部的大型机银行系统存取客户账户、交易信息,通过外部电邮系统给客户发邮件。可以看到,非常简单、清晰,相信不需要解释,都看的明白,里面包含了需要建设的系统本身,系统的客户,和这个系统有交互的周边系统。

用途

这样一个简单的图,可以告诉我们,要构建的系统是什么;它的用户是谁,谁会用它,它要如何融入已有的IT环境。这个图的受众可以是开发团队的内部人员、外部的技术或非技术人员。即:

  • 构建的系统是什么
  • 谁会用它
  • 如何融入已有的IT环境

怎么画

中间是自己的系统,周围是用户和其它与之相互作用的系统。这个图的关键就是梳理清楚待建设系统的用户和高层次的依赖,梳理清楚了画下来只需要几分钟时间。

2、容器图(Container Diagram)

容器图是把语境图里待建设的系统做了一个展开。

上图中,除了用户和外围系统,要建设的系统包括一个基于javaspring mvc的web应用提供系统的功能入口,基于xamarin架构的手机app提供手机端的功能入口,一个基于java的api应用提供服务,一个mysql数据库用于存储,各个应用之间的交互都在箭头线上写明了。

看这张图的时候,不会去关注到图中是直角方框还是圆角方框,不会关注是实线箭头还是虚线箭头,甚至箭头的指向也没有引起太多注意。

我们有许多的画图方式,都对框、线的含义做了定义,这就需要画图的人和看图的人都清晰的理解这些定义,才能读全图里的信息,而现实是,这往往是非常高的一个要求,所以,很多图只能看个大概的含义。

用途

这个图的受众可以是团队内部或外部的开发人员,也可以是运维人员。用途可以罗列为:

  • 展现了软件系统的整体形态
  • 体现了高层次的技术决策
  • 系统中的职责是如何分布的,容器间的是如何交互的
  • 告诉开发者在哪里写代码

怎么画

用一个框图来表示,内部可能包括名称、技术选择、职责,以及这些框图之间的交互,如果涉及外部系统,最好明确边界。

3、组件图(Component Diagram)

组件图是把某个容器进行展开,描述其内部的模块。

用途

这个图主要是给内部开发人员看的,怎么去做代码的组织和构建。其用途有:

  • 描述了系统由哪些组件/服务组成
  • 厘清了组件之间的关系和依赖
  • 为软件开发如何分解交付提供了框架

4、类图(Code/Class Diagram)

这个图很显然是给技术人员看的,比较常见,就不详细介绍了。

案例分享

下面是内部的一个实时数据工具的架构图。作为一个应该自描述的架构图,这里不多做解释了。如果有看不明白的,那肯定是还画的不够好。

画好架构图可能有许多方法论,本篇主要介绍了C4这种方法,C4的理论也是不断进化的。但不论是哪种画图方法论,我们回到画图初衷,更好的交流,我们在画的过程中不必被条条框框所限制。简而言之,画之前想好:画图给谁看,看什么,怎么样不解释就看懂。

作者简介:三画,阿里巴巴技术专家,梓敬、鹏升和余乐对此文亦有贡献。三画曾多年从事工作流引擎研发工作,现专注于高并发移动互联网应用的架构和开发,和本文贡献者均来自阿里巴巴零售通部门。目前零售通大量招Java开发,欢迎有志之士投简历到 lst-wireless@alibaba-inc.com,和我们一起共建智能分销网络,让百万小店拥抱DT时代。



本文作者:三画

阅读原文

本文来自云栖社区合作伙伴“ 阿里技术”,如需转载请联系原作者。

查看原文

不可能的是 赞了文章 · 10月17日

如何画出一张合格的技术架构图?

阿里妹导读:技术传播的价值,不仅仅体现在通过商业化产品和开源项目来缩短我们构建应用的路径,加速业务的上线速率,也体现在优秀工程师在工作效率提升、产品性能优化和用户体验改善等经验方面的分享,以提高我们的专业能力。

接下来,阿里巴巴技术专家三画,将分享自己和团队在画好架构图方面的理念和经验,希望对你有所帮助。

当我们想用一张或几张图来描述我们的系统时,是不是经常遇到以下情况:

  • 对着画布无从下手、删了又来?
  • 如何用一张图描述我的系统,并且让产品、运营、开发都能看明白?
  • 画了一半的图还不清楚受众是谁?
  • 画出来的图到底是产品图功能图还是技术图又或是大杂烩?
  • 图上的框框有点少是不是要找点儿框框加进来?
  • 布局怎么画都不满意……

如果有同样的困惑,本文将介绍一种画图的方法论,来让架构图更清晰。

先厘清一些基础概念

1、什么是架构?

架构就是对系统中的实体以及实体之间的关系所进行的抽象描述,是一系列的决策。

架构是结构和愿景。

系统架构是概念的体现,是对物/信息的功能与形式元素之间的对应情况所做的分配,是对元素之间的关系以及元素同周边环境之间的关系所做的定义。

做好架构是个复杂的任务,也是个很大的话题,本篇就不做深入了。有了架构之后,就需要让干系人理解、遵循相关决策。

2、什么是架构图?

系统架构图是为了抽象地表示软件系统的整体轮廓和各个组件之间的相互关系和约束边界,以及软件系统的物理部署和软件系统的演进方向的整体视图。

3、架构图的作用

一图胜千言。要让干系人理解、遵循架构决策,就需要把架构信息传递出去。架构图就是一个很好的载体。那么,画架构图是为了:

  • 解决沟通障碍
  • 达成共识
  • 减少歧义

4、架构图分类

搜集了很多资料,分类有很多,有一种比较流行的是4+1视图,分别为场景视图、逻辑视图、物理视图、处理流程视图和开发视图。

场景视图

场景视图用于描述系统的参与者与功能用例间的关系,反映系统的最终需求和交互设计,通常由用例图表示。

逻辑视图

逻辑视图用于描述系统软件功能拆解后的组件关系,组件约束和边界,反映系统整体组成与系统如何构建的过程,通常由UML的组件图和类图来表示。

物理视图

物理视图用于描述系统软件到物理硬件的映射关系,反映出系统的组件是如何部署到一组可计算机器节点上,用于指导软件系统的部署实施过程。

处理流程视图

处理流程视图用于描述系统软件组件之间的通信时序,数据的输入输出,反映系统的功能流程与数据流程,通常由时序图和流程图表示。

开发视图

开发视图用于描述系统的模块划分和组成,以及细化到内部包的组成设计,服务于开发人员,反映系统开发实施过程。

以上 5 种架构视图从不同角度表示一个软件系统的不同特征,组合到一起作为架构蓝图描述系统架构。

怎样的架构图是好的架构图

上面的分类是前人的经验总结,图也是从网上摘来的,那么这些图画的好不好呢?是不是我们要依葫芦画瓢去画这样一些图?

先不去管这些图好不好,我们通过对这些图的分类以及作用,思考了一下,总结下来,我们认为,在画出一个好的架构图之前, 首先应该要明确其受众,再想清楚要给他们传递什么信息 ,所以,不要为了画一个物理视图去画物理视图,为了画一个逻辑视图去画逻辑视图,而应该根据受众的不同,传递的信息的不同,用图准确地表达出来,最后的图可能就是在这样一些分类里。那么,画出的图好不好的一个直接标准就是:受众有没有准确接收到想传递的信息。

明确这两点之后,从受众角度来说,一个好的架构图是不需要解释的,它应该是自描述的,并且要具备一致性和足够的准确性,能够与代码相呼应。

画架构图遇到的常见问题

1、方框代表什么?

为什么适用方框而不是圆形,它有什么特殊的含义吗?随意使用方框或者其它形状可能会引起混淆。

2、虚线、实线什么意思?箭头什么意思?颜色什么意思?

随意使用线条或者箭头可能会引起误会。

3、运行时与编译时冲突?层级冲突?

架构是一项复杂的工作,只使用单个图表来表示架构很容易造成莫名其妙的语义混乱。

本文推荐的画图方法

C4 模型使用容器(应用程序、数据存储、微服务等)、组件和代码来描述一个软件系统的静态结构。这几种图比较容易画,也给出了画图要点,但最关键的是,我们认为,它明确指出了每种图可能的受众以及意义。

下面的案例来自C4官网,然后加上了一些我们的理解,来看看如何更好的表达软件架构

1、语境图(System Context Diagram)

这是一个想象的待建设的互联网银行系统,它使用外部的大型机银行系统存取客户账户、交易信息,通过外部电邮系统给客户发邮件。可以看到,非常简单、清晰,相信不需要解释,都看的明白,里面包含了需要建设的系统本身,系统的客户,和这个系统有交互的周边系统。

用途

这样一个简单的图,可以告诉我们,要构建的系统是什么;它的用户是谁,谁会用它,它要如何融入已有的IT环境。这个图的受众可以是开发团队的内部人员、外部的技术或非技术人员。即:

  • 构建的系统是什么
  • 谁会用它
  • 如何融入已有的IT环境

怎么画

中间是自己的系统,周围是用户和其它与之相互作用的系统。这个图的关键就是梳理清楚待建设系统的用户和高层次的依赖,梳理清楚了画下来只需要几分钟时间。

2、容器图(Container Diagram)

容器图是把语境图里待建设的系统做了一个展开。

上图中,除了用户和外围系统,要建设的系统包括一个基于javaspring mvc的web应用提供系统的功能入口,基于xamarin架构的手机app提供手机端的功能入口,一个基于java的api应用提供服务,一个mysql数据库用于存储,各个应用之间的交互都在箭头线上写明了。

看这张图的时候,不会去关注到图中是直角方框还是圆角方框,不会关注是实线箭头还是虚线箭头,甚至箭头的指向也没有引起太多注意。

我们有许多的画图方式,都对框、线的含义做了定义,这就需要画图的人和看图的人都清晰的理解这些定义,才能读全图里的信息,而现实是,这往往是非常高的一个要求,所以,很多图只能看个大概的含义。

用途

这个图的受众可以是团队内部或外部的开发人员,也可以是运维人员。用途可以罗列为:

  • 展现了软件系统的整体形态
  • 体现了高层次的技术决策
  • 系统中的职责是如何分布的,容器间的是如何交互的
  • 告诉开发者在哪里写代码

怎么画

用一个框图来表示,内部可能包括名称、技术选择、职责,以及这些框图之间的交互,如果涉及外部系统,最好明确边界。

3、组件图(Component Diagram)

组件图是把某个容器进行展开,描述其内部的模块。

用途

这个图主要是给内部开发人员看的,怎么去做代码的组织和构建。其用途有:

  • 描述了系统由哪些组件/服务组成
  • 厘清了组件之间的关系和依赖
  • 为软件开发如何分解交付提供了框架

4、类图(Code/Class Diagram)

这个图很显然是给技术人员看的,比较常见,就不详细介绍了。

案例分享

下面是内部的一个实时数据工具的架构图。作为一个应该自描述的架构图,这里不多做解释了。如果有看不明白的,那肯定是还画的不够好。

画好架构图可能有许多方法论,本篇主要介绍了C4这种方法,C4的理论也是不断进化的。但不论是哪种画图方法论,我们回到画图初衷,更好的交流,我们在画的过程中不必被条条框框所限制。简而言之,画之前想好:画图给谁看,看什么,怎么样不解释就看懂。

作者简介:三画,阿里巴巴技术专家,梓敬、鹏升和余乐对此文亦有贡献。三画曾多年从事工作流引擎研发工作,现专注于高并发移动互联网应用的架构和开发,和本文贡献者均来自阿里巴巴零售通部门。目前零售通大量招Java开发,欢迎有志之士投简历到 lst-wireless@alibaba-inc.com,和我们一起共建智能分销网络,让百万小店拥抱DT时代。



本文作者:三画

阅读原文

本文来自云栖社区合作伙伴“ 阿里技术”,如需转载请联系原作者。

查看原文

赞 134 收藏 98 评论 0

不可能的是 收藏了文章 · 9月26日

webpack-chain项目中文翻译

webpack-chain

注意:这是对原项目readme文件的翻译,为啥翻译这个呢,因为Vue CLI3脚手架生成的项目使用这种方式配置webpack,但是脚手架中对这块的介绍不多,所以把这部分翻译出来,以供团队和大家参考。

应用一个链式 API 来生成和简化 2-4 版本的webpack的配置的修改。

此文档对应于webpack-chain的v5版本,对于以前的版本,请参阅:

注意: 虽然 webpack-chain 被广泛应用在Neutrino中,然而本软件包完全独立,可供任何项目使用。

介绍

webpack 的核心配置的创建和修改基于一个有潜在难于处理的 JavaScript 对象。虽然这对于配置单个项目来说还是 OK 的,但当你尝试跨项目共享这些对象并使其进行后续的修改就会变的混乱不堪,因为您需要深入了解底层对象的结构以进行这些更改。

webpack-chain 尝试通过提供可链式或顺流式的 API 创建和修改webpack 配置。API的 Key 部分可以由用户指定的名称引用,这有助于 跨项目修改配置方式 的标准化。

通过以下示例可以更容易地解释这一点。

安装

webpack-chain 需要 Node.js v6.9及更高版本.
webpack-chain 也只创建并被设计于使用webpack的2,3,4版本的配置对象。

你可以使用Yarn或者npm来安装此软件包(俩个包管理工具选一个就行):

Yarn方式

yarn add --dev webpack-chain

npm方式

npm install --save-dev webpack-chain

入门

当你安装了 webpack-chain, 你就可以开始创建一个webpack的配置。 对于本指南,我们的示例基本配置 webpack.config.js 将位于我们项目的根目录。

// 导入 webpack-chain 模块,该模块导出了一个用于创建一个webpack配置API的单一构造函数。
const Config = require('webpack-chain');

// 对该单一构造函数创建一个新的配置实例
const config = new Config();

// 用链式API改变配置
// 每个API的调用都会跟踪对存储配置的更改。

config
  // 修改 entry 配置
  .entry('index')
    .add('src/index.js')
    .end()
  // 修改 output 配置
  .output
    .path('dist')
    .filename('[name].bundle.js');

// 创建一个具名规则,以后用来修改规则
config.module
  .rule('lint')
    .test(/\.js$/)
    .pre()
    .include
      .add('src')
      .end()
    // 还可以创建具名use (loaders)
    .use('eslint')
      .loader('eslint-loader')
      .options({
        rules: {
          semi: 'off'
        }
      });

config.module
  .rule('compile')
    .test(/\.js$/)
    .include
      .add('src')
      .add('test')
      .end()
    .use('babel')
      .loader('babel-loader')
      .options({
        presets: [
          ['@babel/preset-env', { modules: false }]
        ]
      });

// 也可以创建一个具名的插件!
config
  .plugin('clean')
    .use(CleanPlugin, [['dist'], { root: '/dir' }]);

// 导出这个修改完成的要被webpack使用的配置对象
module.exports = config.toConfig();

共享配置也很简单。仅仅导出配置 和 在传递给webpack之前调用 .toConfig() 方法将配置导出给webpack使用。

// webpack.core.js
const Config = require('webpack-chain');
const config = new Config();

// 跨目标共享配置
// Make configuration shared across targets
// ...

module.exports = config;

// webpack.dev.js
const config = require('./webpack.core');

// Dev-specific configuration
// 开发具体配置
// ...
module.exports = config.toConfig();

// webpack.prod.js
const config = require('./webpack.core');

// Production-specific configuration
// 生产具体配置
// ...
module.exports = config.toConfig();

ChainedMap

webpack-chain 中的核心API接口之一是 ChainedMap. 一个 ChainedMap的操作类似于JavaScript Map, 为链式和生成配置提供了一些便利。 如果一个属性被标记一个 ChainedMap, 则它将具有如下的API和方法:

除非另有说明,否则这些方法将返回 ChainedMap , 允许链式调用这些方法。

// 从 Map 移除所有 配置.
clear()
// 通过键值从 Map 移除单个配置.
// key: *
delete(key)
// 获取 Map 中相应键的值
// key: *
// returns: value
get(key)
// 获取 Map 中相应键的值
// 如果键在Map中不存在,则ChainedMap中该键的值会被配置为fn的返回值.
// key: *
// fn: Function () -> value
// returns: value
getOrCompute(key, fn)
// 配置Map中 已存在的键的值
// key: *
// value: *
set(key, value)
// Map中是否存在一个配置值的特定键,返回 真或假
// key: *
// returns: Boolean
has(key)
// 返回 Map中已存储的所有值的数组
// returns: Array
values()
// 返回Map中全部配置的一个对象, 其中 键是这个对象属性,值是相应键的值,
// 如果Map是空,返回 `undefined`
// 使用 `.before() 或 .after()` 的ChainedMap, 则将按照属性名进行排序。
// returns: Object, undefined if empty
entries()
//  提供一个对象,这个对象的属性和值将 映射进 Map。
// 你也可以提供一个数组作为第二个参数以便忽略合并的属性名称。
// obj: Object
// omit: Optional Array
merge(obj, omit)
// 对当前配置上下文执行函数。
// handler: Function -> ChainedMap
  // 一个把ChainedMap实例作为单个参数的函数
batch(handler)
// 条件执行一个函数去继续配置
// condition: Boolean
// whenTruthy: Function -> ChainedMap
  // 当条件为真,调用把ChainedMap实例作为单一参数传入的函数
// whenFalsy: Optional Function -> ChainedMap
  // 当条件为假,调用把ChainedMap实例作为单一参数传入的函数
when(condition, whenTruthy, whenFalsy)

ChainedSet

webpack-chain 中的核心API接口另一个是 ChainedSet. 一个 ChainedSet的操作类似于JavaScript Map, 为链式和生成配置提供了一些便利。 如果一个属性被标记一个 ChainedSet, 则它将具有如下的API和方法:

除非另有说明,否则这些方法将返回 ChainedSet , 允许链式调用这些方法。

// 添加/追加 给Set末尾位置一个值.
// value: *
add(value)
// 添加 给Set开始位置一个值.
// value: *
prepend(value)
// 移除Set中全部值.
clear()
// 移除Set中一个指定的值.
// value: *
delete(value)
// 检测Set中是否存在一个值.
// value: *
// returns: Boolean
has(value)
// 返回Set中值的数组.
// returns: Array
values()
// 连接给定的数组到 Set 尾部。
// arr: Array
merge(arr)

// 对当前配置上下文执行函数。
// handler: Function -> ChainedSet
  // 一个把 ChainedSet 实例作为单个参数的函数
batch(handler)
// 条件执行一个函数去继续配置
// condition: Boolean
// whenTruthy: Function -> ChainedSet
  // 当条件为真,调用把 ChainedSet 实例作为单一参数传入的函数
// whenFalsy: Optional Function -> ChainedSet
  // 当条件为假,调用把 ChainedSet 实例作为单一参数传入的函数
when(condition, whenTruthy, whenFalsy)

速记方法

存在许多简写方法,用于 使用与简写方法名称相同的键在 ChainedMap 设置一个值
例如, devServer.hot 是一个速记方法, 因此它可以用作:

// 在 ChainedMap 上设置一个值的 速记方法
devServer.hot(true);

// 上述方法等效于:
devServer.set('hot', true);

一个速记方法是可链式的,因此调用它将返回 原实例,允许你继续链式使用

配置

创建一个新的配置对象

const Config = require('webpack-chain');

const config = new Config();

移动到API的更深层将改变你正在修改的内容的上下文。 你可以通过 config在此引用顶级配置或者通过调用 .end() 方法向上移动一级 使你移回更高的 上下文环境。
如果你熟悉jQuery, 这里与其 .end() 工作原理类似。除非另有说明,否则全部的API调用都将在当前上下文中返回API实例。 这样,你可以根据需要连续 链式API调用.
有关对所有速记和低级房费有效的特定值的详细信息,请参阅 webpack文档层次结构 中的相应名词。

Config : ChainedMap

配置速记方法

config
  .amd(amd)
  .bail(bail)
  .cache(cache)
  .devtool(devtool)
  .context(context)
  .externals(externals)
  .loader(loader)
  .mode(mode)
  .parallelism(parallelism)
  .profile(profile)
  .recordsPath(recordsPath)
  .recordsInputPath(recordsInputPath)
  .recordsOutputPath(recordsOutputPath)
  .stats(stats)
  .target(target)
  .watch(watch)
  .watchOptions(watchOptions)

配置 entryPoints

// 回到 config.entryPoints : ChainedMap
config.entry(name) : ChainedSet

config
  .entry(name)
    .add(value)
    .add(value)

config
  .entry(name)
    .clear()

// 用低级别 config.entryPoints:

config.entryPoints
  .get(name)
    .add(value)
    .add(value)

config.entryPoints
  .get(name)
    .clear()

配置 output: 速记 方法

config.output : ChainedMap

config.output
  .auxiliaryComment(auxiliaryComment)
  .chunkFilename(chunkFilename)
  .chunkLoadTimeout(chunkLoadTimeout)
  .crossOriginLoading(crossOriginLoading)
  .devtoolFallbackModuleFilenameTemplate(devtoolFallbackModuleFilenameTemplate)
  .devtoolLineToLine(devtoolLineToLine)
  .devtoolModuleFilenameTemplate(devtoolModuleFilenameTemplate)
  .filename(filename)
  .hashFunction(hashFunction)
  .hashDigest(hashDigest)
  .hashDigestLength(hashDigestLength)
  .hashSalt(hashSalt)
  .hotUpdateChunkFilename(hotUpdateChunkFilename)
  .hotUpdateFunction(hotUpdateFunction)
  .hotUpdateMainFilename(hotUpdateMainFilename)
  .jsonpFunction(jsonpFunction)
  .library(library)
  .libraryExport(libraryExport)
  .libraryTarget(libraryTarget)
  .path(path)
  .pathinfo(pathinfo)
  .publicPath(publicPath)
  .sourceMapFilename(sourceMapFilename)
  .sourcePrefix(sourcePrefix)
  .strictModuleExceptionHandling(strictModuleExceptionHandling)
  .umdNamedDefine(umdNamedDefine)

配置 resolve(解析): 速记方法

config.resolve : ChainedMap

config.resolve
  .cachePredicate(cachePredicate)
  .cacheWithContext(cacheWithContext)
  .enforceExtension(enforceExtension)
  .enforceModuleExtension(enforceModuleExtension)
  .unsafeCache(unsafeCache)
  .symlinks(symlinks)

配置 resolve 别名

config.resolve.alias : ChainedMap

config.resolve.alias
  .set(key, value)
  .set(key, value)
  .delete(key)
  .clear()

配置 resolve modules

config.resolve.modules : ChainedSet

config.resolve.modules
  .add(value)
  .prepend(value)
  .clear()

配置 resolve aliasFields

config.resolve.aliasFields : ChainedSet

config.resolve.aliasFields
  .add(value)
  .prepend(value)
  .clear()

配置 resolve descriptionFields

config.resolve.descriptionFields : ChainedSet

config.resolve.descriptionFields
  .add(value)
  .prepend(value)
  .clear()

配置 resolve extensions

config.resolve.extensions : ChainedSet

config.resolve.extensions
  .add(value)
  .prepend(value)
  .clear()

配置 resolve mainFields

config.resolve.mainFields : ChainedSet

config.resolve.mainFields
  .add(value)
  .prepend(value)
  .clear()

配置 resolve mainFiles

config.resolve.mainFiles : ChainedSet

config.resolve.mainFiles
  .add(value)
  .prepend(value)
  .clear()

配置 resolveLoader

当前API config.resolveLoader 相同于 配置 config.resolve 用下面的配置:

配置 resolveLoader moduleExtensions
config.resolveLoader.moduleExtensions : ChainedSet

config.resolveLoader.moduleExtensions
  .add(value)
  .prepend(value)
  .clear()
配置 resolveLoader packageMains
config.resolveLoader.packageMains : ChainedSet

config.resolveLoader.packageMains
  .add(value)
  .prepend(value)
  .clear()

配置 performance(性能): 速记方法

config.performance : ChainedMap

config.performance
  .hints(hints)
  .maxEntrypointSize(maxEntrypointSize)
  .maxAssetSize(maxAssetSize)
  .assetFilter(assetFilter)

配置 optimizations(优化): 速记方法

config.optimization : ChainedMap

config.optimization
  .concatenateModules(concatenateModules)
  .flagIncludedChunks(flagIncludedChunks)
  .mergeDuplicateChunks(mergeDuplicateChunks)
  .minimize(minimize)
  .namedChunks(namedChunks)
  .namedModules(namedModules)
  .nodeEnv(nodeEnv)
  .noEmitOnErrors(noEmitOnErrors)
  .occurrenceOrder(occurrenceOrder)
  .portableRecords(portableRecords)
  .providedExports(providedExports)
  .removeAvailableModules(removeAvailableModules)
  .removeEmptyChunks(removeEmptyChunks)
  .runtimeChunk(runtimeChunk)
  .sideEffects(sideEffects)
  .splitChunks(splitChunks)
  .usedExports(usedExports)

配置 optimization minimizers(最小优化器)

// 回到 config.optimization.minimizers
config.optimization
  .minimizer(name) : ChainedMap

配置 optimization minimizers: 添加

注意: 不要用 new 去创建最小优化器插件,因为已经为你做好了。

config.optimization
  .minimizer(name)
  .use(WebpackPlugin, args)

// 例如

config.optimization
  .minimizer('css')
  .use(OptimizeCSSAssetsPlugin, [{ cssProcessorOptions: { safe: true } }])

// Minimizer 插件也可以由它们的路径指定,从而允许在不使用插件或webpack配置的情况下跳过昂贵的 require s。
config.optimization
  .minimizer('css')
  .use(require.resolve('optimize-css-assets-webpack-plugin'), [{ cssProcessorOptions: { safe: true } }])

配置 optimization minimizers: 修改参数

config.optimization
  .minimizer(name)
  .tap(args => newArgs)

// 例如
config
  .minimizer('css')
  .tap(args => [...args, { cssProcessorOptions: { safe: false } }])

配置 optimization minimizers: 修改实例

config.optimization
  .minimizer(name)
  .init((Plugin, args) => new Plugin(...args));

配置 optimization minimizers: 移除

config.optimization.minimizers.delete(name)

配置插件

// 回到 config.plugins
config.plugin(name) : ChainedMap

配置插件: 添加

注意: 不要用 new 去创建插件,因为已经为你做好了。

config
  .plugin(name)
  .use(WebpackPlugin, args)

// 例如
config
  .plugin('hot')
  .use(webpack.HotModuleReplacementPlugin);

// 插件也可以由它们的路径指定,从而允许在不使用插件或webpack配置的情况下跳过昂贵的 require s。
config
  .plugin('env')
  .use(require.resolve('webpack/lib/EnvironmentPlugin'), [{ 'VAR': false }]);

配置插件: 修改参数

config
  .plugin(name)
  .tap(args => newArgs)

// 例如
config
  .plugin('env')
  .tap(args => [...args, 'SECRET_KEY']);

配置插件: 修改实例

config
  .plugin(name)
  .init((Plugin, args) => new Plugin(...args));

配置插件: 移除

config.plugins.delete(name)

配置插件: 在之前调用

指定当前插件上下文应该在另一个指定插件之前执行,你不能在同一个插件上同时使用 .before().after()

config
  .plugin(name)
    .before(otherName)

// 例如
config
  .plugin('html-template')
    .use(HtmlWebpackTemplate)
    .end()
  .plugin('script-ext')
    .use(ScriptExtWebpackPlugin)
    .before('html-template');

Config plugins: 在之后调用

指定当前插件上下文应该在另一个指定插件之后执行,你不能在同一个插件上同时使用 .before().after()

config
  .plugin(name)
    .after(otherName)

// 例如
config
  .plugin('html-template')
    .after('script-ext')
    .use(HtmlWebpackTemplate)
    .end()
  .plugin('script-ext')
    .use(ScriptExtWebpackPlugin);

配置 resolve 插件

// 回到 config.resolve.plugins
config.resolve.plugin(name) : ChainedMap

配置 resolve 插件: 添加

注意: 不要用 new 去创建插件,因为已经为你做好了。

config.resolve
  .plugin(name)
  .use(WebpackPlugin, args)

配置 resolve 插件: 修改参数

config.resolve
  .plugin(name)
  .tap(args => newArgs)

配置 resolve 插件: 修改实例

config.resolve
  .plugin(name)
  .init((Plugin, args) => new Plugin(...args))

配置 resolve 插件: 移除

config.resolve.plugins.delete(name)

配置 resolve 插件: 在之前调用

指定当前插件上下文应该在另一个指定插件之前执行,你不能在同一个插件上同时使用 .before().after()

config.resolve
  .plugin(name)
    .before(otherName)

// 例如

config.resolve
  .plugin('beta')
    .use(BetaWebpackPlugin)
    .end()
  .plugin('alpha')
    .use(AlphaWebpackPlugin)
    .before('beta');

配置 resolve 插件: 在之后调用

指定当前插件上下文应该在另一个指定插件之后执行,你不能在同一个插件上同时使用 .before().after()

config.resolve
  .plugin(name)
    .after(otherName)

// 例如
config.resolve
  .plugin('beta')
    .after('alpha')
    .use(BetaWebpackTemplate)
    .end()
  .plugin('alpha')
    .use(AlphaWebpackPlugin);

配置 node

config.node : ChainedMap

config.node
  .set('__dirname', 'mock')
  .set('__filename', 'mock');

配置 devServer

config.devServer : ChainedMap

配置 devServer allowedHosts

config.devServer.allowedHosts : ChainedSet

config.devServer.allowedHosts
  .add(value)
  .prepend(value)
  .clear()

配置 devServer: 速记方法

config.devServer
  .bonjour(bonjour)
  .clientLogLevel(clientLogLevel)
  .color(color)
  .compress(compress)
  .contentBase(contentBase)
  .disableHostCheck(disableHostCheck)
  .filename(filename)
  .headers(headers)
  .historyApiFallback(historyApiFallback)
  .host(host)
  .hot(hot)
  .hotOnly(hotOnly)
  .https(https)
  .inline(inline)
  .info(info)
  .lazy(lazy)
  .noInfo(noInfo)
  .open(open)
  .openPage(openPage)
  .overlay(overlay)
  .pfx(pfx)
  .pfxPassphrase(pfxPassphrase)
  .port(port)
  .progress(progress)
  .proxy(proxy)
  .public(public)
  .publicPath(publicPath)
  .quiet(quiet)
  .setup(setup)
  .socket(socket)
  .staticOptions(staticOptions)
  .stats(stats)
  .stdin(stdin)
  .useLocalIp(useLocalIp)
  .watchContentBase(watchContentBase)
  .watchOptions(watchOptions)

配置 module

config.module : ChainedMap

配置 module: 速记方法

config.module : ChainedMap

config.module
  .noParse(noParse)

配置 module rules: 速记方法

config.module.rules : ChainedMap

config.module
  .rule(name)
    .test(test)
    .pre()
    .post()
    .enforce(preOrPost)

配置 module rules uses (loaders): 创建

config.module.rules{}.uses : ChainedMap

config.module
  .rule(name)
    .use(name)
      .loader(loader)
      .options(options)

// Example

config.module
  .rule('compile')
    .use('babel')
      .loader('babel-loader')
      .options({ presets: ['@babel/preset-env'] });

配置 module rules uses (loaders): 修改选项

config.module
  .rule(name)
    .use(name)
      .tap(options => newOptions)

// 例如

config.module
  .rule('compile')
    .use('babel')
      .tap(options => merge(options, {
        plugins: ['@babel/plugin-proposal-class-properties']
      }));

配置 module rules oneOfs (条件 rules)

config.module.rules{}.oneOfs : ChainedMap<Rule>

config.module
  .rule(name)
    .oneOf(name)

// 例如

config.module
  .rule('css')
    .oneOf('inline')
      .resourceQuery(/inline/)
      .use('url')
        .loader('url-loader')
        .end()
      .end()
    .oneOf('external')
      .resourceQuery(/external/)
      .use('file')
        .loader('file-loader')

合并配置

webpack-chain 支持将对象合并到配置实例,改实例类似于 webpack-chain 模式 布局的布局。 请注意,这不是 webpack 配置对象,但您可以再将webpack配置对象提供给webpack-chain 以匹配器布局之前对其进行转换。

config.merge({ devtool: 'source-map' });

config.get('devtool') // "source-map"
config.merge({
  [key]: value,

  amd,
  bail,
  cache,
  context,
  devtool,
  externals,
  loader,
  mode,
  parallelism,
  profile,
  recordsPath,
  recordsInputPath,
  recordsOutputPath,
  stats,
  target,
  watch,
  watchOptions,

  entry: {
    [name]: [...values]
  },

  plugin: {
    [name]: {
      plugin: WebpackPlugin,
      args: [...args],
      before,
      after
    }
  },

  devServer: {
    [key]: value,

    clientLogLevel,
    compress,
    contentBase,
    filename,
    headers,
    historyApiFallback,
    host,
    hot,
    hotOnly,
    https,
    inline,
    lazy,
    noInfo,
    overlay,
    port,
    proxy,
    quiet,
    setup,
    stats,
    watchContentBase
  },

  node: {
    [key]: value
  },

  optimizations: {
    concatenateModules,
    flagIncludedChunks,
    mergeDuplicateChunks,
    minimize,
    minimizer,
    namedChunks,
    namedModules,
    nodeEnv,
    noEmitOnErrors,
    occurrenceOrder,
    portableRecords,
    providedExports,
    removeAvailableModules,
    removeEmptyChunks,
    runtimeChunk,
    sideEffects,
    splitChunks,
    usedExports,
  },

  performance: {
    [key]: value,

    hints,
    maxEntrypointSize,
    maxAssetSize,
    assetFilter
  },

  resolve: {
    [key]: value,

    alias: {
      [key]: value
    },
    aliasFields: [...values],
    descriptionFields: [...values],
    extensions: [...values],
    mainFields: [...values],
    mainFiles: [...values],
    modules: [...values],

    plugin: {
      [name]: {
        plugin: WebpackPlugin,
        args: [...args],
        before,
        after
      }
    }
  },

  resolveLoader: {
    [key]: value,

    alias: {
      [key]: value
    },
    aliasFields: [...values],
    descriptionFields: [...values],
    extensions: [...values],
    mainFields: [...values],
    mainFiles: [...values],
    modules: [...values],
    moduleExtensions: [...values],
    packageMains: [...values],

    plugin: {
      [name]: {
        plugin: WebpackPlugin,
        args: [...args],
        before,
        after
      }
    }
  },

  module: {
    [key]: value,

    rule: {
      [name]: {
        [key]: value,

        enforce,
        issuer,
        parser,
        resource,
        resourceQuery,
        test,

        include: [...paths],
        exclude: [...paths],

        oneOf: {
          [name]: Rule
        },

        use: {
          [name]: {
            loader: LoaderString,
            options: LoaderOptions,
            before,
            after
          }
        }
      }
    }
  }
})

条件配置

当使用的情况下工作ChainedMap和ChainedSet,则可以使用执行条件的配置when。您必须指定一个表达式 when(),以评估其真实性或虚假性。如果表达式是真实的,则将使用当前链接实例的实例调用第一个函数参数。您可以选择提供在条件为假时调用的第二个函数,该函数也是当前链接的实例。

// 示例:仅在生产期间添加minify插件
config
  .when(process.env.NODE_ENV === 'production', config => {
    config
      .plugin('minify')
      .use(BabiliWebpackPlugin);
  });
// 例:只有在生产过程中添加缩小插件,否则设置devtool到源映射
config
  .when(process.env.NODE_ENV === 'production',
    config => config.plugin('minify').use(BabiliWebpackPlugin),
    config => config.devtool('source-map')
  );

检查生成的配置

您可以使用检查生成的webpack配置config.toString()。这将生成配置的字符串化版本,其中包含命名规则,用法和插件的注释提示:

config
  .module
    .rule('compile')
      .test(/\.js$/)
      .use('babel')
        .loader('babel-loader');

config.toString();


{
  module: {
    rules: [
      /* config.module.rule('compile') */
      {
        test: /\.js$/,
        use: [
          /* config.module.rule('compile').use('babel') */
          {
            loader: 'babel-loader'
          }
        ]
      }
    ]
  }
}

默认情况下,如果生成的字符串包含需要的函数和插件,则不能直接用作真正的webpack配置。为了生成可用的配置,您可以通过__expression在其上设置特殊属性来自定义函数和插件的字符串化方式:

class MyPlugin {}
MyPlugin.__expression = `require('my-plugin')`;

function myFunction () {}
myFunction.__expression = `require('my-function')`;

config
  .plugin('example')
    .use(MyPlugin, [{ fn: myFunction }]);

config.toString();

/*
{
  plugins: [
    new (require('my-plugin'))({
      fn: require('my-function')
    })
  ]
}
*/

通过其路径指定的插件将require()自动生成其语句:

config
  .plugin('env')
    .use(require.resolve('webpack/lib/ProvidePlugin'), [{ jQuery: 'jquery' }])

config.toString();


{
  plugins: [
    new (require('/foo/bar/src/node_modules/webpack/lib/EnvironmentPlugin.js'))(
      {
        jQuery: 'jquery'
      }
    )
  ]
}

您还可以调用toString静态方法Config,以便在字符串化之前修改配置对象。

Config.toString({
  ...config.toConfig(),
  module: {
    defaultRules: [
      {
        use: [
          {
            loader: 'banner-loader',
            options: { prefix: 'banner-prefix.txt' },
          },
        ],
      },
    ],
  },
})


{
  plugins: [
    /* config.plugin('foo') */
    new TestPlugin()
  ],
  module: {
    defaultRules: [
      {
        use: [
          {
            loader: 'banner-loader',
            options: {
              prefix: 'banner-prefix.txt'
            }
          }
        ]
      }
    ]
  }
}
查看原文

不可能的是 赞了文章 · 9月26日

webpack-chain项目中文翻译

webpack-chain

注意:这是对原项目readme文件的翻译,为啥翻译这个呢,因为Vue CLI3脚手架生成的项目使用这种方式配置webpack,但是脚手架中对这块的介绍不多,所以把这部分翻译出来,以供团队和大家参考。

应用一个链式 API 来生成和简化 2-4 版本的webpack的配置的修改。

此文档对应于webpack-chain的v5版本,对于以前的版本,请参阅:

注意: 虽然 webpack-chain 被广泛应用在Neutrino中,然而本软件包完全独立,可供任何项目使用。

介绍

webpack 的核心配置的创建和修改基于一个有潜在难于处理的 JavaScript 对象。虽然这对于配置单个项目来说还是 OK 的,但当你尝试跨项目共享这些对象并使其进行后续的修改就会变的混乱不堪,因为您需要深入了解底层对象的结构以进行这些更改。

webpack-chain 尝试通过提供可链式或顺流式的 API 创建和修改webpack 配置。API的 Key 部分可以由用户指定的名称引用,这有助于 跨项目修改配置方式 的标准化。

通过以下示例可以更容易地解释这一点。

安装

webpack-chain 需要 Node.js v6.9及更高版本.
webpack-chain 也只创建并被设计于使用webpack的2,3,4版本的配置对象。

你可以使用Yarn或者npm来安装此软件包(俩个包管理工具选一个就行):

Yarn方式

yarn add --dev webpack-chain

npm方式

npm install --save-dev webpack-chain

入门

当你安装了 webpack-chain, 你就可以开始创建一个webpack的配置。 对于本指南,我们的示例基本配置 webpack.config.js 将位于我们项目的根目录。

// 导入 webpack-chain 模块,该模块导出了一个用于创建一个webpack配置API的单一构造函数。
const Config = require('webpack-chain');

// 对该单一构造函数创建一个新的配置实例
const config = new Config();

// 用链式API改变配置
// 每个API的调用都会跟踪对存储配置的更改。

config
  // 修改 entry 配置
  .entry('index')
    .add('src/index.js')
    .end()
  // 修改 output 配置
  .output
    .path('dist')
    .filename('[name].bundle.js');

// 创建一个具名规则,以后用来修改规则
config.module
  .rule('lint')
    .test(/\.js$/)
    .pre()
    .include
      .add('src')
      .end()
    // 还可以创建具名use (loaders)
    .use('eslint')
      .loader('eslint-loader')
      .options({
        rules: {
          semi: 'off'
        }
      });

config.module
  .rule('compile')
    .test(/\.js$/)
    .include
      .add('src')
      .add('test')
      .end()
    .use('babel')
      .loader('babel-loader')
      .options({
        presets: [
          ['@babel/preset-env', { modules: false }]
        ]
      });

// 也可以创建一个具名的插件!
config
  .plugin('clean')
    .use(CleanPlugin, [['dist'], { root: '/dir' }]);

// 导出这个修改完成的要被webpack使用的配置对象
module.exports = config.toConfig();

共享配置也很简单。仅仅导出配置 和 在传递给webpack之前调用 .toConfig() 方法将配置导出给webpack使用。

// webpack.core.js
const Config = require('webpack-chain');
const config = new Config();

// 跨目标共享配置
// Make configuration shared across targets
// ...

module.exports = config;

// webpack.dev.js
const config = require('./webpack.core');

// Dev-specific configuration
// 开发具体配置
// ...
module.exports = config.toConfig();

// webpack.prod.js
const config = require('./webpack.core');

// Production-specific configuration
// 生产具体配置
// ...
module.exports = config.toConfig();

ChainedMap

webpack-chain 中的核心API接口之一是 ChainedMap. 一个 ChainedMap的操作类似于JavaScript Map, 为链式和生成配置提供了一些便利。 如果一个属性被标记一个 ChainedMap, 则它将具有如下的API和方法:

除非另有说明,否则这些方法将返回 ChainedMap , 允许链式调用这些方法。

// 从 Map 移除所有 配置.
clear()
// 通过键值从 Map 移除单个配置.
// key: *
delete(key)
// 获取 Map 中相应键的值
// key: *
// returns: value
get(key)
// 获取 Map 中相应键的值
// 如果键在Map中不存在,则ChainedMap中该键的值会被配置为fn的返回值.
// key: *
// fn: Function () -> value
// returns: value
getOrCompute(key, fn)
// 配置Map中 已存在的键的值
// key: *
// value: *
set(key, value)
// Map中是否存在一个配置值的特定键,返回 真或假
// key: *
// returns: Boolean
has(key)
// 返回 Map中已存储的所有值的数组
// returns: Array
values()
// 返回Map中全部配置的一个对象, 其中 键是这个对象属性,值是相应键的值,
// 如果Map是空,返回 `undefined`
// 使用 `.before() 或 .after()` 的ChainedMap, 则将按照属性名进行排序。
// returns: Object, undefined if empty
entries()
//  提供一个对象,这个对象的属性和值将 映射进 Map。
// 你也可以提供一个数组作为第二个参数以便忽略合并的属性名称。
// obj: Object
// omit: Optional Array
merge(obj, omit)
// 对当前配置上下文执行函数。
// handler: Function -> ChainedMap
  // 一个把ChainedMap实例作为单个参数的函数
batch(handler)
// 条件执行一个函数去继续配置
// condition: Boolean
// whenTruthy: Function -> ChainedMap
  // 当条件为真,调用把ChainedMap实例作为单一参数传入的函数
// whenFalsy: Optional Function -> ChainedMap
  // 当条件为假,调用把ChainedMap实例作为单一参数传入的函数
when(condition, whenTruthy, whenFalsy)

ChainedSet

webpack-chain 中的核心API接口另一个是 ChainedSet. 一个 ChainedSet的操作类似于JavaScript Map, 为链式和生成配置提供了一些便利。 如果一个属性被标记一个 ChainedSet, 则它将具有如下的API和方法:

除非另有说明,否则这些方法将返回 ChainedSet , 允许链式调用这些方法。

// 添加/追加 给Set末尾位置一个值.
// value: *
add(value)
// 添加 给Set开始位置一个值.
// value: *
prepend(value)
// 移除Set中全部值.
clear()
// 移除Set中一个指定的值.
// value: *
delete(value)
// 检测Set中是否存在一个值.
// value: *
// returns: Boolean
has(value)
// 返回Set中值的数组.
// returns: Array
values()
// 连接给定的数组到 Set 尾部。
// arr: Array
merge(arr)

// 对当前配置上下文执行函数。
// handler: Function -> ChainedSet
  // 一个把 ChainedSet 实例作为单个参数的函数
batch(handler)
// 条件执行一个函数去继续配置
// condition: Boolean
// whenTruthy: Function -> ChainedSet
  // 当条件为真,调用把 ChainedSet 实例作为单一参数传入的函数
// whenFalsy: Optional Function -> ChainedSet
  // 当条件为假,调用把 ChainedSet 实例作为单一参数传入的函数
when(condition, whenTruthy, whenFalsy)

速记方法

存在许多简写方法,用于 使用与简写方法名称相同的键在 ChainedMap 设置一个值
例如, devServer.hot 是一个速记方法, 因此它可以用作:

// 在 ChainedMap 上设置一个值的 速记方法
devServer.hot(true);

// 上述方法等效于:
devServer.set('hot', true);

一个速记方法是可链式的,因此调用它将返回 原实例,允许你继续链式使用

配置

创建一个新的配置对象

const Config = require('webpack-chain');

const config = new Config();

移动到API的更深层将改变你正在修改的内容的上下文。 你可以通过 config在此引用顶级配置或者通过调用 .end() 方法向上移动一级 使你移回更高的 上下文环境。
如果你熟悉jQuery, 这里与其 .end() 工作原理类似。除非另有说明,否则全部的API调用都将在当前上下文中返回API实例。 这样,你可以根据需要连续 链式API调用.
有关对所有速记和低级房费有效的特定值的详细信息,请参阅 webpack文档层次结构 中的相应名词。

Config : ChainedMap

配置速记方法

config
  .amd(amd)
  .bail(bail)
  .cache(cache)
  .devtool(devtool)
  .context(context)
  .externals(externals)
  .loader(loader)
  .mode(mode)
  .parallelism(parallelism)
  .profile(profile)
  .recordsPath(recordsPath)
  .recordsInputPath(recordsInputPath)
  .recordsOutputPath(recordsOutputPath)
  .stats(stats)
  .target(target)
  .watch(watch)
  .watchOptions(watchOptions)

配置 entryPoints

// 回到 config.entryPoints : ChainedMap
config.entry(name) : ChainedSet

config
  .entry(name)
    .add(value)
    .add(value)

config
  .entry(name)
    .clear()

// 用低级别 config.entryPoints:

config.entryPoints
  .get(name)
    .add(value)
    .add(value)

config.entryPoints
  .get(name)
    .clear()

配置 output: 速记 方法

config.output : ChainedMap

config.output
  .auxiliaryComment(auxiliaryComment)
  .chunkFilename(chunkFilename)
  .chunkLoadTimeout(chunkLoadTimeout)
  .crossOriginLoading(crossOriginLoading)
  .devtoolFallbackModuleFilenameTemplate(devtoolFallbackModuleFilenameTemplate)
  .devtoolLineToLine(devtoolLineToLine)
  .devtoolModuleFilenameTemplate(devtoolModuleFilenameTemplate)
  .filename(filename)
  .hashFunction(hashFunction)
  .hashDigest(hashDigest)
  .hashDigestLength(hashDigestLength)
  .hashSalt(hashSalt)
  .hotUpdateChunkFilename(hotUpdateChunkFilename)
  .hotUpdateFunction(hotUpdateFunction)
  .hotUpdateMainFilename(hotUpdateMainFilename)
  .jsonpFunction(jsonpFunction)
  .library(library)
  .libraryExport(libraryExport)
  .libraryTarget(libraryTarget)
  .path(path)
  .pathinfo(pathinfo)
  .publicPath(publicPath)
  .sourceMapFilename(sourceMapFilename)
  .sourcePrefix(sourcePrefix)
  .strictModuleExceptionHandling(strictModuleExceptionHandling)
  .umdNamedDefine(umdNamedDefine)

配置 resolve(解析): 速记方法

config.resolve : ChainedMap

config.resolve
  .cachePredicate(cachePredicate)
  .cacheWithContext(cacheWithContext)
  .enforceExtension(enforceExtension)
  .enforceModuleExtension(enforceModuleExtension)
  .unsafeCache(unsafeCache)
  .symlinks(symlinks)

配置 resolve 别名

config.resolve.alias : ChainedMap

config.resolve.alias
  .set(key, value)
  .set(key, value)
  .delete(key)
  .clear()

配置 resolve modules

config.resolve.modules : ChainedSet

config.resolve.modules
  .add(value)
  .prepend(value)
  .clear()

配置 resolve aliasFields

config.resolve.aliasFields : ChainedSet

config.resolve.aliasFields
  .add(value)
  .prepend(value)
  .clear()

配置 resolve descriptionFields

config.resolve.descriptionFields : ChainedSet

config.resolve.descriptionFields
  .add(value)
  .prepend(value)
  .clear()

配置 resolve extensions

config.resolve.extensions : ChainedSet

config.resolve.extensions
  .add(value)
  .prepend(value)
  .clear()

配置 resolve mainFields

config.resolve.mainFields : ChainedSet

config.resolve.mainFields
  .add(value)
  .prepend(value)
  .clear()

配置 resolve mainFiles

config.resolve.mainFiles : ChainedSet

config.resolve.mainFiles
  .add(value)
  .prepend(value)
  .clear()

配置 resolveLoader

当前API config.resolveLoader 相同于 配置 config.resolve 用下面的配置:

配置 resolveLoader moduleExtensions
config.resolveLoader.moduleExtensions : ChainedSet

config.resolveLoader.moduleExtensions
  .add(value)
  .prepend(value)
  .clear()
配置 resolveLoader packageMains
config.resolveLoader.packageMains : ChainedSet

config.resolveLoader.packageMains
  .add(value)
  .prepend(value)
  .clear()

配置 performance(性能): 速记方法

config.performance : ChainedMap

config.performance
  .hints(hints)
  .maxEntrypointSize(maxEntrypointSize)
  .maxAssetSize(maxAssetSize)
  .assetFilter(assetFilter)

配置 optimizations(优化): 速记方法

config.optimization : ChainedMap

config.optimization
  .concatenateModules(concatenateModules)
  .flagIncludedChunks(flagIncludedChunks)
  .mergeDuplicateChunks(mergeDuplicateChunks)
  .minimize(minimize)
  .namedChunks(namedChunks)
  .namedModules(namedModules)
  .nodeEnv(nodeEnv)
  .noEmitOnErrors(noEmitOnErrors)
  .occurrenceOrder(occurrenceOrder)
  .portableRecords(portableRecords)
  .providedExports(providedExports)
  .removeAvailableModules(removeAvailableModules)
  .removeEmptyChunks(removeEmptyChunks)
  .runtimeChunk(runtimeChunk)
  .sideEffects(sideEffects)
  .splitChunks(splitChunks)
  .usedExports(usedExports)

配置 optimization minimizers(最小优化器)

// 回到 config.optimization.minimizers
config.optimization
  .minimizer(name) : ChainedMap

配置 optimization minimizers: 添加

注意: 不要用 new 去创建最小优化器插件,因为已经为你做好了。

config.optimization
  .minimizer(name)
  .use(WebpackPlugin, args)

// 例如

config.optimization
  .minimizer('css')
  .use(OptimizeCSSAssetsPlugin, [{ cssProcessorOptions: { safe: true } }])

// Minimizer 插件也可以由它们的路径指定,从而允许在不使用插件或webpack配置的情况下跳过昂贵的 require s。
config.optimization
  .minimizer('css')
  .use(require.resolve('optimize-css-assets-webpack-plugin'), [{ cssProcessorOptions: { safe: true } }])

配置 optimization minimizers: 修改参数

config.optimization
  .minimizer(name)
  .tap(args => newArgs)

// 例如
config
  .minimizer('css')
  .tap(args => [...args, { cssProcessorOptions: { safe: false } }])

配置 optimization minimizers: 修改实例

config.optimization
  .minimizer(name)
  .init((Plugin, args) => new Plugin(...args));

配置 optimization minimizers: 移除

config.optimization.minimizers.delete(name)

配置插件

// 回到 config.plugins
config.plugin(name) : ChainedMap

配置插件: 添加

注意: 不要用 new 去创建插件,因为已经为你做好了。

config
  .plugin(name)
  .use(WebpackPlugin, args)

// 例如
config
  .plugin('hot')
  .use(webpack.HotModuleReplacementPlugin);

// 插件也可以由它们的路径指定,从而允许在不使用插件或webpack配置的情况下跳过昂贵的 require s。
config
  .plugin('env')
  .use(require.resolve('webpack/lib/EnvironmentPlugin'), [{ 'VAR': false }]);

配置插件: 修改参数

config
  .plugin(name)
  .tap(args => newArgs)

// 例如
config
  .plugin('env')
  .tap(args => [...args, 'SECRET_KEY']);

配置插件: 修改实例

config
  .plugin(name)
  .init((Plugin, args) => new Plugin(...args));

配置插件: 移除

config.plugins.delete(name)

配置插件: 在之前调用

指定当前插件上下文应该在另一个指定插件之前执行,你不能在同一个插件上同时使用 .before().after()

config
  .plugin(name)
    .before(otherName)

// 例如
config
  .plugin('html-template')
    .use(HtmlWebpackTemplate)
    .end()
  .plugin('script-ext')
    .use(ScriptExtWebpackPlugin)
    .before('html-template');

Config plugins: 在之后调用

指定当前插件上下文应该在另一个指定插件之后执行,你不能在同一个插件上同时使用 .before().after()

config
  .plugin(name)
    .after(otherName)

// 例如
config
  .plugin('html-template')
    .after('script-ext')
    .use(HtmlWebpackTemplate)
    .end()
  .plugin('script-ext')
    .use(ScriptExtWebpackPlugin);

配置 resolve 插件

// 回到 config.resolve.plugins
config.resolve.plugin(name) : ChainedMap

配置 resolve 插件: 添加

注意: 不要用 new 去创建插件,因为已经为你做好了。

config.resolve
  .plugin(name)
  .use(WebpackPlugin, args)

配置 resolve 插件: 修改参数

config.resolve
  .plugin(name)
  .tap(args => newArgs)

配置 resolve 插件: 修改实例

config.resolve
  .plugin(name)
  .init((Plugin, args) => new Plugin(...args))

配置 resolve 插件: 移除

config.resolve.plugins.delete(name)

配置 resolve 插件: 在之前调用

指定当前插件上下文应该在另一个指定插件之前执行,你不能在同一个插件上同时使用 .before().after()

config.resolve
  .plugin(name)
    .before(otherName)

// 例如

config.resolve
  .plugin('beta')
    .use(BetaWebpackPlugin)
    .end()
  .plugin('alpha')
    .use(AlphaWebpackPlugin)
    .before('beta');

配置 resolve 插件: 在之后调用

指定当前插件上下文应该在另一个指定插件之后执行,你不能在同一个插件上同时使用 .before().after()

config.resolve
  .plugin(name)
    .after(otherName)

// 例如
config.resolve
  .plugin('beta')
    .after('alpha')
    .use(BetaWebpackTemplate)
    .end()
  .plugin('alpha')
    .use(AlphaWebpackPlugin);

配置 node

config.node : ChainedMap

config.node
  .set('__dirname', 'mock')
  .set('__filename', 'mock');

配置 devServer

config.devServer : ChainedMap

配置 devServer allowedHosts

config.devServer.allowedHosts : ChainedSet

config.devServer.allowedHosts
  .add(value)
  .prepend(value)
  .clear()

配置 devServer: 速记方法

config.devServer
  .bonjour(bonjour)
  .clientLogLevel(clientLogLevel)
  .color(color)
  .compress(compress)
  .contentBase(contentBase)
  .disableHostCheck(disableHostCheck)
  .filename(filename)
  .headers(headers)
  .historyApiFallback(historyApiFallback)
  .host(host)
  .hot(hot)
  .hotOnly(hotOnly)
  .https(https)
  .inline(inline)
  .info(info)
  .lazy(lazy)
  .noInfo(noInfo)
  .open(open)
  .openPage(openPage)
  .overlay(overlay)
  .pfx(pfx)
  .pfxPassphrase(pfxPassphrase)
  .port(port)
  .progress(progress)
  .proxy(proxy)
  .public(public)
  .publicPath(publicPath)
  .quiet(quiet)
  .setup(setup)
  .socket(socket)
  .staticOptions(staticOptions)
  .stats(stats)
  .stdin(stdin)
  .useLocalIp(useLocalIp)
  .watchContentBase(watchContentBase)
  .watchOptions(watchOptions)

配置 module

config.module : ChainedMap

配置 module: 速记方法

config.module : ChainedMap

config.module
  .noParse(noParse)

配置 module rules: 速记方法

config.module.rules : ChainedMap

config.module
  .rule(name)
    .test(test)
    .pre()
    .post()
    .enforce(preOrPost)

配置 module rules uses (loaders): 创建

config.module.rules{}.uses : ChainedMap

config.module
  .rule(name)
    .use(name)
      .loader(loader)
      .options(options)

// Example

config.module
  .rule('compile')
    .use('babel')
      .loader('babel-loader')
      .options({ presets: ['@babel/preset-env'] });

配置 module rules uses (loaders): 修改选项

config.module
  .rule(name)
    .use(name)
      .tap(options => newOptions)

// 例如

config.module
  .rule('compile')
    .use('babel')
      .tap(options => merge(options, {
        plugins: ['@babel/plugin-proposal-class-properties']
      }));

配置 module rules oneOfs (条件 rules)

config.module.rules{}.oneOfs : ChainedMap<Rule>

config.module
  .rule(name)
    .oneOf(name)

// 例如

config.module
  .rule('css')
    .oneOf('inline')
      .resourceQuery(/inline/)
      .use('url')
        .loader('url-loader')
        .end()
      .end()
    .oneOf('external')
      .resourceQuery(/external/)
      .use('file')
        .loader('file-loader')

合并配置

webpack-chain 支持将对象合并到配置实例,改实例类似于 webpack-chain 模式 布局的布局。 请注意,这不是 webpack 配置对象,但您可以再将webpack配置对象提供给webpack-chain 以匹配器布局之前对其进行转换。

config.merge({ devtool: 'source-map' });

config.get('devtool') // "source-map"
config.merge({
  [key]: value,

  amd,
  bail,
  cache,
  context,
  devtool,
  externals,
  loader,
  mode,
  parallelism,
  profile,
  recordsPath,
  recordsInputPath,
  recordsOutputPath,
  stats,
  target,
  watch,
  watchOptions,

  entry: {
    [name]: [...values]
  },

  plugin: {
    [name]: {
      plugin: WebpackPlugin,
      args: [...args],
      before,
      after
    }
  },

  devServer: {
    [key]: value,

    clientLogLevel,
    compress,
    contentBase,
    filename,
    headers,
    historyApiFallback,
    host,
    hot,
    hotOnly,
    https,
    inline,
    lazy,
    noInfo,
    overlay,
    port,
    proxy,
    quiet,
    setup,
    stats,
    watchContentBase
  },

  node: {
    [key]: value
  },

  optimizations: {
    concatenateModules,
    flagIncludedChunks,
    mergeDuplicateChunks,
    minimize,
    minimizer,
    namedChunks,
    namedModules,
    nodeEnv,
    noEmitOnErrors,
    occurrenceOrder,
    portableRecords,
    providedExports,
    removeAvailableModules,
    removeEmptyChunks,
    runtimeChunk,
    sideEffects,
    splitChunks,
    usedExports,
  },

  performance: {
    [key]: value,

    hints,
    maxEntrypointSize,
    maxAssetSize,
    assetFilter
  },

  resolve: {
    [key]: value,

    alias: {
      [key]: value
    },
    aliasFields: [...values],
    descriptionFields: [...values],
    extensions: [...values],
    mainFields: [...values],
    mainFiles: [...values],
    modules: [...values],

    plugin: {
      [name]: {
        plugin: WebpackPlugin,
        args: [...args],
        before,
        after
      }
    }
  },

  resolveLoader: {
    [key]: value,

    alias: {
      [key]: value
    },
    aliasFields: [...values],
    descriptionFields: [...values],
    extensions: [...values],
    mainFields: [...values],
    mainFiles: [...values],
    modules: [...values],
    moduleExtensions: [...values],
    packageMains: [...values],

    plugin: {
      [name]: {
        plugin: WebpackPlugin,
        args: [...args],
        before,
        after
      }
    }
  },

  module: {
    [key]: value,

    rule: {
      [name]: {
        [key]: value,

        enforce,
        issuer,
        parser,
        resource,
        resourceQuery,
        test,

        include: [...paths],
        exclude: [...paths],

        oneOf: {
          [name]: Rule
        },

        use: {
          [name]: {
            loader: LoaderString,
            options: LoaderOptions,
            before,
            after
          }
        }
      }
    }
  }
})

条件配置

当使用的情况下工作ChainedMap和ChainedSet,则可以使用执行条件的配置when。您必须指定一个表达式 when(),以评估其真实性或虚假性。如果表达式是真实的,则将使用当前链接实例的实例调用第一个函数参数。您可以选择提供在条件为假时调用的第二个函数,该函数也是当前链接的实例。

// 示例:仅在生产期间添加minify插件
config
  .when(process.env.NODE_ENV === 'production', config => {
    config
      .plugin('minify')
      .use(BabiliWebpackPlugin);
  });
// 例:只有在生产过程中添加缩小插件,否则设置devtool到源映射
config
  .when(process.env.NODE_ENV === 'production',
    config => config.plugin('minify').use(BabiliWebpackPlugin),
    config => config.devtool('source-map')
  );

检查生成的配置

您可以使用检查生成的webpack配置config.toString()。这将生成配置的字符串化版本,其中包含命名规则,用法和插件的注释提示:

config
  .module
    .rule('compile')
      .test(/\.js$/)
      .use('babel')
        .loader('babel-loader');

config.toString();


{
  module: {
    rules: [
      /* config.module.rule('compile') */
      {
        test: /\.js$/,
        use: [
          /* config.module.rule('compile').use('babel') */
          {
            loader: 'babel-loader'
          }
        ]
      }
    ]
  }
}

默认情况下,如果生成的字符串包含需要的函数和插件,则不能直接用作真正的webpack配置。为了生成可用的配置,您可以通过__expression在其上设置特殊属性来自定义函数和插件的字符串化方式:

class MyPlugin {}
MyPlugin.__expression = `require('my-plugin')`;

function myFunction () {}
myFunction.__expression = `require('my-function')`;

config
  .plugin('example')
    .use(MyPlugin, [{ fn: myFunction }]);

config.toString();

/*
{
  plugins: [
    new (require('my-plugin'))({
      fn: require('my-function')
    })
  ]
}
*/

通过其路径指定的插件将require()自动生成其语句:

config
  .plugin('env')
    .use(require.resolve('webpack/lib/ProvidePlugin'), [{ jQuery: 'jquery' }])

config.toString();


{
  plugins: [
    new (require('/foo/bar/src/node_modules/webpack/lib/EnvironmentPlugin.js'))(
      {
        jQuery: 'jquery'
      }
    )
  ]
}

您还可以调用toString静态方法Config,以便在字符串化之前修改配置对象。

Config.toString({
  ...config.toConfig(),
  module: {
    defaultRules: [
      {
        use: [
          {
            loader: 'banner-loader',
            options: { prefix: 'banner-prefix.txt' },
          },
        ],
      },
    ],
  },
})


{
  plugins: [
    /* config.plugin('foo') */
    new TestPlugin()
  ],
  module: {
    defaultRules: [
      {
        use: [
          {
            loader: 'banner-loader',
            options: {
              prefix: 'banner-prefix.txt'
            }
          }
        ]
      }
    ]
  }
}
查看原文

赞 54 收藏 28 评论 4

不可能的是 回答了问题 · 9月3日

eslint 如何配置只扫描当前改动文件

如果是想在开发环境下,热更新时eslint,只扫描改动的文件,应该是没法做到的。

如果是在 package.json文件中,添加lint或者lintfix脚本,可以做到只扫描改动文件

Diff=$(git diff master... --name-only -- "*.ts" "*.tsx" "*.js" "*.jsx")
eslint $Diff

关注 2 回答 1

不可能的是 回答了问题 · 9月3日

element table 内启用表单验证后 el-form-item 不垂直居中

启用表单校验后,el-form-item类上会设置margin-bottom属性值,为error信息占位,所以导致了表单元素偏高。

可以通过覆盖原样式

.el-form-item {
  margin-bottom: 0px
}

.el-form-item.is-error {
  margin-bottom: ??px
}

只在出现错误提示时,才设置下边距

关注 3 回答 2

不可能的是 回答了问题 · 9月3日

Vue通过组件的type类型封装动态组件

用vue动态组件就能满足吧,

<component v-bind:is="currentTabComponent"></component>

遍历时,传入不同组件名,

关注 2 回答 1

不可能的是 收藏了文章 · 8月26日

React 最佳实践——那些 React 没告诉你但很重要的事

前言:对很多 react 新手来说,网上能找到的资源大都是些简单的 tutorial ,它们能教会你如何使用 react ,但并不会告诉你怎么在实际项目中优雅的组织和编写 react 代码。用谷歌搜中文“ React 最佳实践”发现前两页几乎全都是同一篇国外文章的译文...所以我总结了下自己过去那个项目使用 React 踩过的一些坑,也整理了一些别人的观点,希望对部分 react 使用者有帮助。

React 与 AJAX

React只负责处理View这一层,它本身不涉及网络请求/AJAX,所以这里我们需求考虑两个问题:

  • 第一,用什么技术从服务端获取数据;
  • 第二,获取到的数据应该放在react组件的什么位置。

React官方提供了一种解决方案:Load Initial Data via AJAX

使用jQuery的Ajax方法,在一个组件的componentDidMount()中发ajax请求,拿到的数据存在组件自己的state中,并调用setState方法去更新UI。如果是异步获取数据,则在componentWillUnmount中取消发送请求。

如果只是为了使用jQuery的Ajax方法就引入整个jQuery库,既是一种浪费又加大了整个应用的体积。那我们还有什么其他的选择吗?事实上是有很多的:fetch()fetch polyfillaxios...其中最需要我们关注的是window.fetch(),它是一个简洁、标准化的javascript的Ajax API。在Chrome和Firefox中已经可以使用,如果需要兼容其他浏览器,可以使用fetch polyfill。

React官方文档只告诉了我们在一个单一组件中如何通过ajax从服务器端获取数据,但并没有告诉我们在一个完整的实际项目中到底应该把数据存在哪些组件中,这部分如果缺乏规范的话,会导致整个项目变得混乱、难以维护。下面给出三种比较好的实践:

1. 所有的数据请求和管理都存放在唯一的一个根组件

让父组件/根组件集中发送所有的ajax请求,把从服务端获取的数据统一存放在这个组件的state中,再通过props把数据传给子组件。这种方法主要是针对组件树不是很复杂的小型应用。缺点就是当组件树的层级变多了以后,需要把数据一层一层地传给子组件,写起来麻烦,性能也不好。

2. 设置多个容器组件专门处理数据请求和管理

其实跟第一种方法类似,只不过设置多个容器组件来负责数据请求和状态管理。这里我们需要区分两种不同类型的组件,一种是展示性组件(presentational component),另一种是容器性组件(container component)。展示性组件本身不拥有任何状态,所有的数据都从容器组件中获得,在容器组件中发送ajax请求。两者更详细的描述,可以阅读下这篇文章:Presentational and Container Components

一个具体的例子:

假设我们需要展示用户的姓名和头像,首先创建一个展示性组件<UserProfile />,它接受两个Props:nameprofileImage。这个组件内部没有任何关于Ajax的代码。

然后创建一个容器组件<UserProfileContainer />,它接受一个userId的参数,发送Ajax请求从服务器获取数据存在state中,再通过props传给<UserProfile />组件。

3. 使用Redux或Relay的情况

Redux管理状态和数据,Ajax从服务器端获取数据,所以很显然当我们使用了Redux时,应该把所有的网络请求都交给redux来解决。具体来说,应该是放在Async Actions。如果用其他类Flux库的话,解决方式都差不多,都是在actions中发送网络请求。

Relay是Facebook官方推出的一个库。如果用它的话,我们只需要通过GraphQL来声明组件需要的数据,Relay会自动地把下载数据并通过props往下传递。不过想要用Relay,你得先有一个GraphQL的服务器...

一个标准组件的组织结构

1 class definition
    1.1 constructor
        1.1.1 event handlers
    1.2 'component' lifecycle events
    1.3 getters
    1.4 render
2 defaultProps
3 proptypes

示例:

class Person extends React.Component {
  constructor (props) {
    super(props);

    this.state = { smiling: false };

    this.handleClick = () => {
      this.setState({smiling: !this.state.smiling});
    };
  }

  componentWillMount () {
    // add event listeners (Flux Store, WebSocket, document, etc.)
  },

  componentDidMount () {
    // React.getDOMNode()
  },

  componentWillUnmount () {
    // remove event listeners (Flux Store, WebSocket, document, etc.)
  },

  get smilingMessage () {
    return (this.state.smiling) ? "is smiling" : "";
  }

  render () {
    return (
      <div onClick={this.handleClick}>
        {this.props.name} {this.smilingMessage}
      </div>
    );
  },
}

Person.defaultProps = {
  name: 'Guest'
};

Person.propTypes = {
  name: React.PropTypes.string
};

以上示例代码的来源:https://github.com/planningce...

使用 PropTypes 和 getDefaultProps()

  1. 一定要写PropTypes,切莫为了省事而不写
  2. 如果一个Props不是requied,一定在getDefaultProps中设置它

React.PropTypes主要用来验证组件接收到的props是否为正确的数据类型,如果不正确,console中就会出现对应的warning。出于性能方面的考虑,这个API只在开发环境下使用。

基本使用方法:

propTypes: {
    myArray: React.PropTypes.array,
    myBool: React.PropTypes.bool,
    myFunc: React.PropTypes.func,
    myNumber: React.PropTypes.number,
    myString: React.PropTypes.string,
     
     // You can chain any of the above with `isRequired` to make sure a warning
    // is shown if the prop isn't provided.
    requiredFunc: React.PropTypes.func.isRequired
}

假如我们props不是以上类型,而是拥有复杂结构的对象怎么办?比如下面这个:

{
  text: 'hello world',
  numbers: [5, 2, 7, 9],
}

当然,我们可以直接用React.PropTypes.object,但是对象内部的数据我们却无法验证。

propTypes: {
  myObject: React.PropTypes.object,
}

进阶使用方法:shape()arrayOf()

propTypes: {
  myObject: React.PropTypes.shape({
    text: React.PropTypes.string,
    numbers: React.PropTypes.arrayOf(React.PropTypes.number),
  })
}

下面是一个更复杂的Props:

[
  {
    name: 'Zachary He',
    age: 13,
    married: true,
  },
  {
    name: 'Alice Yo',
    name: 17,
  },
  {
    name: 'Jonyu Me',
    age: 20,
    married: false,
  }
]

综合上面,写起来应该就不难了:

propTypes: {
    myArray: React.PropTypes.arrayOf(
        React.propTypes.shape({
            name: React.propTypes.string.isRequired,
            age: React.propTypes.number.isRequired,
            married: React.propTypes.bool
        })
    )
}

把计算和条件判断都交给 render() 方法吧

1. 组件的state中不能出现props
 // BAD:
  constructor (props) {
    this.state = {
      fullName: `${props.firstName} ${props.lastName}`
    };
  }

  render () {
    var fullName = this.state.fullName;
    return (
      <div>
        <h2>{fullName}</h2>
      </div>
    );
  }
// GOOD: 
render () {
  var fullName = `${this.props.firstName} ${this.props.lastName}`;
}

当然,复杂的display logic也应该避免全堆放在render()中,因为那样可能导致整个render()方法变得臃肿,不优雅。我们可以把一些复杂的逻辑通过helper function移出去。

// GOOD: helper function
renderFullName () {
  return `${this.props.firstName} ${this.props.lastName}`;
}

render () {
  var fullName = this.renderFullName();
}
2. 保持state的简洁,不要出现计算得来的state
// WRONG:
  constructor (props) {
    this.state = {
      listItems: [1, 2, 3, 4, 5, 6],
      itemsNum: this.state.listItems.length
    };
  }
  render() {
      return (
          <div>
              <span>{this.state.itemsNum}</span>
          </div>
      )
  }
// Right:
render () {
  var itemsNum = this.state.listItems.length;
}
3. 能用三元判断符,就不用If,直接放在render()里
// BAD: 
renderSmilingStatement () {
    if (this.state.isSmiling) {
        return <span>is smiling</span>;
    }else {
        return '';
    }
},

render () {
  return <div>{this.props.name}{this.renderSmilingStatement()}</div>;
}
// GOOD: 
render () {
  return (
    <div>
      {this.props.name}
      {(this.state.smiling)
        ? <span>is smiling</span>
        : null
      }
    </div>
  );
}
4. 布尔值都不能搞定的,交给IIFE吧

Immediately-invoked function expression

return (
  <section>
    <h1>Color</h1>
    <h3>Name</h3>
    <p>{this.state.color || "white"}</p>
    <h3>Hex</h3>
    <p>
      {(() => {
        switch (this.state.color) {
          case "red":   return "#FF0000";
          case "green": return "#00FF00";
          case "blue":  return "#0000FF";
          default:      return "#FFFFFF";
        }
      })()}
    </p>
  </section>
);
5. 不要把display logic写在componentWillReceivePropscomponentWillMount中,把它们都移到render()中去。

如何动态处理 classNames

1. 使用布尔值
// BAD:
constructor () {
    this.state = {
      classes: []
    };
  }

  handleClick () {
    var classes = this.state.classes;
    var index = classes.indexOf('active');

    if (index != -1) {
      classes.splice(index, 1);
    } else {
      classes.push('active');
    }

    this.setState({ classes: classes });
  }
// GOOD:
  constructor () {
    this.state = {
      isActive: false
    };
  }

  handleClick () {
    this.setState({ isActive: !this.state.isActive });
  }
2. 使用classnames这个小工具来拼接classNames:
// BEFORE:
var Button = React.createClass({
  render () {
    var btnClass = 'btn';
    if (this.state.isPressed) btnClass += ' btn-pressed';
    else if (this.state.isHovered) btnClass += ' btn-over';
    return <button className={btnClass}>{this.props.label}</button>;
  }
});
// AFTER:
var classNames = require('classnames');

var Button = React.createClass({
  render () {
    var btnClass = classNames({
      'btn': true,
      'btn-pressed': this.state.isPressed,
      'btn-over': !this.state.isPressed && this.state.isHovered
    });
    return <button className={btnClass}>{this.props.label}</button>;
  }
});

未完待续...

查看原文

不可能的是 赞了文章 · 8月26日

React 最佳实践——那些 React 没告诉你但很重要的事

前言:对很多 react 新手来说,网上能找到的资源大都是些简单的 tutorial ,它们能教会你如何使用 react ,但并不会告诉你怎么在实际项目中优雅的组织和编写 react 代码。用谷歌搜中文“ React 最佳实践”发现前两页几乎全都是同一篇国外文章的译文...所以我总结了下自己过去那个项目使用 React 踩过的一些坑,也整理了一些别人的观点,希望对部分 react 使用者有帮助。

React 与 AJAX

React只负责处理View这一层,它本身不涉及网络请求/AJAX,所以这里我们需求考虑两个问题:

  • 第一,用什么技术从服务端获取数据;
  • 第二,获取到的数据应该放在react组件的什么位置。

React官方提供了一种解决方案:Load Initial Data via AJAX

使用jQuery的Ajax方法,在一个组件的componentDidMount()中发ajax请求,拿到的数据存在组件自己的state中,并调用setState方法去更新UI。如果是异步获取数据,则在componentWillUnmount中取消发送请求。

如果只是为了使用jQuery的Ajax方法就引入整个jQuery库,既是一种浪费又加大了整个应用的体积。那我们还有什么其他的选择吗?事实上是有很多的:fetch()fetch polyfillaxios...其中最需要我们关注的是window.fetch(),它是一个简洁、标准化的javascript的Ajax API。在Chrome和Firefox中已经可以使用,如果需要兼容其他浏览器,可以使用fetch polyfill。

React官方文档只告诉了我们在一个单一组件中如何通过ajax从服务器端获取数据,但并没有告诉我们在一个完整的实际项目中到底应该把数据存在哪些组件中,这部分如果缺乏规范的话,会导致整个项目变得混乱、难以维护。下面给出三种比较好的实践:

1. 所有的数据请求和管理都存放在唯一的一个根组件

让父组件/根组件集中发送所有的ajax请求,把从服务端获取的数据统一存放在这个组件的state中,再通过props把数据传给子组件。这种方法主要是针对组件树不是很复杂的小型应用。缺点就是当组件树的层级变多了以后,需要把数据一层一层地传给子组件,写起来麻烦,性能也不好。

2. 设置多个容器组件专门处理数据请求和管理

其实跟第一种方法类似,只不过设置多个容器组件来负责数据请求和状态管理。这里我们需要区分两种不同类型的组件,一种是展示性组件(presentational component),另一种是容器性组件(container component)。展示性组件本身不拥有任何状态,所有的数据都从容器组件中获得,在容器组件中发送ajax请求。两者更详细的描述,可以阅读下这篇文章:Presentational and Container Components

一个具体的例子:

假设我们需要展示用户的姓名和头像,首先创建一个展示性组件<UserProfile />,它接受两个Props:nameprofileImage。这个组件内部没有任何关于Ajax的代码。

然后创建一个容器组件<UserProfileContainer />,它接受一个userId的参数,发送Ajax请求从服务器获取数据存在state中,再通过props传给<UserProfile />组件。

3. 使用Redux或Relay的情况

Redux管理状态和数据,Ajax从服务器端获取数据,所以很显然当我们使用了Redux时,应该把所有的网络请求都交给redux来解决。具体来说,应该是放在Async Actions。如果用其他类Flux库的话,解决方式都差不多,都是在actions中发送网络请求。

Relay是Facebook官方推出的一个库。如果用它的话,我们只需要通过GraphQL来声明组件需要的数据,Relay会自动地把下载数据并通过props往下传递。不过想要用Relay,你得先有一个GraphQL的服务器...

一个标准组件的组织结构

1 class definition
    1.1 constructor
        1.1.1 event handlers
    1.2 'component' lifecycle events
    1.3 getters
    1.4 render
2 defaultProps
3 proptypes

示例:

class Person extends React.Component {
  constructor (props) {
    super(props);

    this.state = { smiling: false };

    this.handleClick = () => {
      this.setState({smiling: !this.state.smiling});
    };
  }

  componentWillMount () {
    // add event listeners (Flux Store, WebSocket, document, etc.)
  },

  componentDidMount () {
    // React.getDOMNode()
  },

  componentWillUnmount () {
    // remove event listeners (Flux Store, WebSocket, document, etc.)
  },

  get smilingMessage () {
    return (this.state.smiling) ? "is smiling" : "";
  }

  render () {
    return (
      <div onClick={this.handleClick}>
        {this.props.name} {this.smilingMessage}
      </div>
    );
  },
}

Person.defaultProps = {
  name: 'Guest'
};

Person.propTypes = {
  name: React.PropTypes.string
};

以上示例代码的来源:https://github.com/planningce...

使用 PropTypes 和 getDefaultProps()

  1. 一定要写PropTypes,切莫为了省事而不写
  2. 如果一个Props不是requied,一定在getDefaultProps中设置它

React.PropTypes主要用来验证组件接收到的props是否为正确的数据类型,如果不正确,console中就会出现对应的warning。出于性能方面的考虑,这个API只在开发环境下使用。

基本使用方法:

propTypes: {
    myArray: React.PropTypes.array,
    myBool: React.PropTypes.bool,
    myFunc: React.PropTypes.func,
    myNumber: React.PropTypes.number,
    myString: React.PropTypes.string,
     
     // You can chain any of the above with `isRequired` to make sure a warning
    // is shown if the prop isn't provided.
    requiredFunc: React.PropTypes.func.isRequired
}

假如我们props不是以上类型,而是拥有复杂结构的对象怎么办?比如下面这个:

{
  text: 'hello world',
  numbers: [5, 2, 7, 9],
}

当然,我们可以直接用React.PropTypes.object,但是对象内部的数据我们却无法验证。

propTypes: {
  myObject: React.PropTypes.object,
}

进阶使用方法:shape()arrayOf()

propTypes: {
  myObject: React.PropTypes.shape({
    text: React.PropTypes.string,
    numbers: React.PropTypes.arrayOf(React.PropTypes.number),
  })
}

下面是一个更复杂的Props:

[
  {
    name: 'Zachary He',
    age: 13,
    married: true,
  },
  {
    name: 'Alice Yo',
    name: 17,
  },
  {
    name: 'Jonyu Me',
    age: 20,
    married: false,
  }
]

综合上面,写起来应该就不难了:

propTypes: {
    myArray: React.PropTypes.arrayOf(
        React.propTypes.shape({
            name: React.propTypes.string.isRequired,
            age: React.propTypes.number.isRequired,
            married: React.propTypes.bool
        })
    )
}

把计算和条件判断都交给 render() 方法吧

1. 组件的state中不能出现props
 // BAD:
  constructor (props) {
    this.state = {
      fullName: `${props.firstName} ${props.lastName}`
    };
  }

  render () {
    var fullName = this.state.fullName;
    return (
      <div>
        <h2>{fullName}</h2>
      </div>
    );
  }
// GOOD: 
render () {
  var fullName = `${this.props.firstName} ${this.props.lastName}`;
}

当然,复杂的display logic也应该避免全堆放在render()中,因为那样可能导致整个render()方法变得臃肿,不优雅。我们可以把一些复杂的逻辑通过helper function移出去。

// GOOD: helper function
renderFullName () {
  return `${this.props.firstName} ${this.props.lastName}`;
}

render () {
  var fullName = this.renderFullName();
}
2. 保持state的简洁,不要出现计算得来的state
// WRONG:
  constructor (props) {
    this.state = {
      listItems: [1, 2, 3, 4, 5, 6],
      itemsNum: this.state.listItems.length
    };
  }
  render() {
      return (
          <div>
              <span>{this.state.itemsNum}</span>
          </div>
      )
  }
// Right:
render () {
  var itemsNum = this.state.listItems.length;
}
3. 能用三元判断符,就不用If,直接放在render()里
// BAD: 
renderSmilingStatement () {
    if (this.state.isSmiling) {
        return <span>is smiling</span>;
    }else {
        return '';
    }
},

render () {
  return <div>{this.props.name}{this.renderSmilingStatement()}</div>;
}
// GOOD: 
render () {
  return (
    <div>
      {this.props.name}
      {(this.state.smiling)
        ? <span>is smiling</span>
        : null
      }
    </div>
  );
}
4. 布尔值都不能搞定的,交给IIFE吧

Immediately-invoked function expression

return (
  <section>
    <h1>Color</h1>
    <h3>Name</h3>
    <p>{this.state.color || "white"}</p>
    <h3>Hex</h3>
    <p>
      {(() => {
        switch (this.state.color) {
          case "red":   return "#FF0000";
          case "green": return "#00FF00";
          case "blue":  return "#0000FF";
          default:      return "#FFFFFF";
        }
      })()}
    </p>
  </section>
);
5. 不要把display logic写在componentWillReceivePropscomponentWillMount中,把它们都移到render()中去。

如何动态处理 classNames

1. 使用布尔值
// BAD:
constructor () {
    this.state = {
      classes: []
    };
  }

  handleClick () {
    var classes = this.state.classes;
    var index = classes.indexOf('active');

    if (index != -1) {
      classes.splice(index, 1);
    } else {
      classes.push('active');
    }

    this.setState({ classes: classes });
  }
// GOOD:
  constructor () {
    this.state = {
      isActive: false
    };
  }

  handleClick () {
    this.setState({ isActive: !this.state.isActive });
  }
2. 使用classnames这个小工具来拼接classNames:
// BEFORE:
var Button = React.createClass({
  render () {
    var btnClass = 'btn';
    if (this.state.isPressed) btnClass += ' btn-pressed';
    else if (this.state.isHovered) btnClass += ' btn-over';
    return <button className={btnClass}>{this.props.label}</button>;
  }
});
// AFTER:
var classNames = require('classnames');

var Button = React.createClass({
  render () {
    var btnClass = classNames({
      'btn': true,
      'btn-pressed': this.state.isPressed,
      'btn-over': !this.state.isPressed && this.state.isHovered
    });
    return <button className={btnClass}>{this.props.label}</button>;
  }
});

未完待续...

查看原文

赞 85 收藏 329 评论 14

不可能的是 回答了问题 · 8月23日

eslint不允许vue模板中的html换行怎么解决?

把eslint 里面的 max-len 或者 Prettier 里面的printWidth, 设置小点试试

关注 2 回答 1

认证与成就

  • 获得 218 次点赞
  • 获得 12 枚徽章 获得 1 枚金徽章, 获得 6 枚银徽章, 获得 5 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2017-08-12
个人主页被 1.9k 人浏览