Giter Club home page Giter Club logo

blog's People

Contributors

waltcow avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

blog's Issues

swift4语法备忘-Subscripts

Subscripts

Classes, structures, and enumerations can define subscripts, which are shortcuts for accessing the member elements of a collection. For example, you access elements in an Array instance as someArray[index] and elements in a Dictionary instance as someDictionary[key].

You can define multiple subscripts for a single type, and the appropriate subscript overload to use is selected based on the type of index value you pass to the subscript. Subscripts are not limited to a single dimension, and you can define subscripts with multiple input parameters to suit your custom type’s needs.

Syntax

subscript(index: Int) -> Int {
    get {
        // return an appropriate subscript value here
    }
    set(newValue) {
        // perform a suitable setting action here
    }
}

//read-only computed properties
subscript(index: Int) -> Int {
    // return an appropriate subscript value here
}

PC版微信数据库解密

最近看了几篇wechat 数据库解密的分享, 感觉很有意思尝试了一下,现在此进行总结整理。

PC版微信的聊天记录数据库是放在本地的,其目录为 \文档 \WeChat Files\用户名\Msg 。数据库是经过sqlcipher加密过的,密码生成算法在内存中加密的。当在微信登录过程中,可以用Ollydbg在内存中找到解密的秘钥,通过给出的解密算法便可以解密出内容(通过sqlcipher也可以解密)。Msg目录中的.db文件都可以解密,其中ChatMsg.db存放的是聊天记录

通过Ollydbg调试微信获取秘钥

下载并安装Ollydbg

启动微信,停留在登录界面, 先不要登录,用 Ollydbg 附加微信,进程名WeChat

image

在外部模块 【Alt + E】 找到 wechatwin.dll 模块

image

搜索字符串

image

定位到 ”DBFactory::encryptDB”

image

test edx, edx处下断点【F2】

image

【Ctrl + F2】 重新运行起来,登录微信,跑入刚刚下的断点处,edx中即为秘钥地址,秘钥长度32位。
【Ctrl + G】输入内存地址,定位内存,复制出32 位长度的秘钥,记下来留在下面解密用到

image

解密数据库

安装好C++ IDE, 下面以CLion为例

解密需要用到openssl库,win10请用1.0.2版本,为避免各种问题,可以直接使用slproweb提供的安装版,切勿使用名字含Light的轻量版 , 下面以Win32OpenSSL-1_0_2p.exe 这个版本为例, 安装路径选择 C:\OpenSSL-Win32

CMakeLists.txt

cmake_minimum_required(VERSION 3.12)
project(wechat_db_decrypt)

set(CMAKE_CXX_STANDARD 14)

set(INC_DIR C:\\OpenSSL-Win32\\include)

set(LINK_DIR C:\\OpenSSL-Win32\\lib)

include_directories(${INC_DIR})
link_directories(${LINK_DIR})
link_directories(C:\\OpenSSL-Win32\\lib\\MinGW)

add_executable(wechat_db_decrypt main.cpp)

target_link_libraries(wechat_db_decrypt eay32)

main.cpp

#include <Windows.h>
#include <openssl/rand.h>
#include <openssl/evp.h>
#include <openssl/aes.h>
#include <openssl/hmac.h>

//#define ANDROID_WECHAT

#define SQLITE_FILE_HEADER "SQLite format 3" //length == 16
#define IV_SIZE 16
#define HMAC_SHA1_SIZE 20
#define KEY_SIZE 32

#ifndef ANDROID_WECHAT
#define DEFAULT_PAGESIZE 4096
#define DEFAULT_ITER 64000
#else
#define NO_USE_HMAC_SHA1
#define DEFAULT_PAGESIZE 1024
#define DEFAULT_ITER 4000
#endif


//安卓端这里密码是7位,pc端是经过算法得到的32位pass,这里的0xcc是我隐去的部分,实际不会有0xcc
unsigned char pass[] = {
        0xD7, 0xB9, 0x54, 0xED, 0x71, 0x97, 0x40, 0xDF, 0xA5, 0xC0, 0x91, 0x16, 0x08, 0x04, 0xF5, 0xF4,
        0xCA, 0x0C, 0x65, 0x0B, 0x8A, 0x0B, 0x45, 0xCA, 0x80, 0xA1, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc
};


int main()
{
    FILE *fpdb = fopen("MicroMsg.db", "rb+");

    if (!fpdb) {
        printf("error open file");
        return 0;
    }
    fseek(fpdb, 0, SEEK_END);
    long nFileSize = ftell(fpdb);
    fseek(fpdb, 0, SEEK_SET);
    unsigned char *pDbBuffer = new unsigned char[nFileSize];
    fread(pDbBuffer, 1, nFileSize, fpdb);
    fclose(fpdb);

    unsigned char salt[16] = { 0 };
    memcpy(salt, pDbBuffer, 16);

#ifndef NO_USE_HMAC_SHA1
    unsigned char mac_salt[16] = { 0 };
    memcpy(mac_salt, salt, 16);
    for (int i = 0; i < sizeof(salt); i++) {
        mac_salt[i] ^= 0x3a;
    }
#endif

    int reserve = IV_SIZE;
#ifndef NO_USE_HMAC_SHA1
    reserve += HMAC_SHA1_SIZE;
#endif
    reserve = ((reserve % AES_BLOCK_SIZE) == 0) ? reserve : ((reserve / AES_BLOCK_SIZE) + 1) * AES_BLOCK_SIZE;

    unsigned char key[KEY_SIZE] = { 0 };
    unsigned char mac_key[KEY_SIZE] = { 0 };

    OpenSSL_add_all_algorithms();
    PKCS5_PBKDF2_HMAC_SHA1((const char *)pass, sizeof(pass), salt, sizeof(salt), DEFAULT_ITER, sizeof(key), key);
#ifndef NO_USE_HMAC_SHA1
    PKCS5_PBKDF2_HMAC_SHA1((const char *)key, sizeof(key), mac_salt, sizeof(mac_salt), 2, sizeof(mac_key), mac_key);
#endif

    unsigned char *pTemp = pDbBuffer;
    unsigned char pDecryptPerPageBuffer[DEFAULT_PAGESIZE];
    int nPage = 1;
    int offset = 16;
    while (pTemp < pDbBuffer + nFileSize) {
        printf("decrypt page:%d/%d \n", nPage, nFileSize / DEFAULT_PAGESIZE);

#ifndef NO_USE_HMAC_SHA1
        //check hmac
        unsigned char hash_mac[HMAC_SHA1_SIZE] = { 0 };
        unsigned int hash_len = 0;
        HMAC_CTX hctx;
        HMAC_CTX_init(&hctx);
        HMAC_Init_ex(&hctx, mac_key, sizeof(mac_key), EVP_sha1(), NULL);
        HMAC_Update(&hctx, pTemp + offset, DEFAULT_PAGESIZE - reserve - offset + IV_SIZE);
        HMAC_Update(&hctx, (const unsigned char *)&nPage, sizeof(nPage));
        HMAC_Final(&hctx, hash_mac, &hash_len);
        HMAC_CTX_cleanup(&hctx);
        if (0 != memcmp(hash_mac, pTemp + DEFAULT_PAGESIZE - reserve + IV_SIZE, sizeof(hash_mac))) {
            //hash check err
            return 0;
        }
#endif
        //
        if (nPage == 1) {
            memcpy(pDecryptPerPageBuffer, SQLITE_FILE_HEADER, offset);
        }

        //aes decrypt
        EVP_CIPHER_CTX* ectx = EVP_CIPHER_CTX_new();
        EVP_CipherInit_ex(ectx, EVP_get_cipherbyname("aes-256-cbc"), NULL, NULL, NULL, 0);
        EVP_CIPHER_CTX_set_padding(ectx, 0);
        EVP_CipherInit_ex(ectx, NULL, NULL, key, pTemp + (DEFAULT_PAGESIZE - reserve), 0);

        int nDecryptLen = 0;
        int nTotal = 0;
        EVP_CipherUpdate(ectx, pDecryptPerPageBuffer + offset, &nDecryptLen, pTemp + offset, DEFAULT_PAGESIZE - reserve - offset);
        nTotal = nDecryptLen;
        EVP_CipherFinal_ex(ectx, pDecryptPerPageBuffer + offset + nDecryptLen, &nDecryptLen);
        nTotal += nDecryptLen;
        EVP_CIPHER_CTX_free(ectx);

        //assert(nTotal == DEFAULT_PAGESIZE - reserve - offset);

        //no necessary ,just like sqlcipher
        memcpy(pDecryptPerPageBuffer + DEFAULT_PAGESIZE - reserve, pTemp + DEFAULT_PAGESIZE - reserve, reserve);

        FILE *fp = fopen("MicroMsg_Decrypt.db", "ab+");
        {
            fwrite(pDecryptPerPageBuffer, 1, DEFAULT_PAGESIZE, fp);
            fclose(fp);
        }

        nPage++;
        offset = 0;
        pTemp += DEFAULT_PAGESIZE;
    }
    return 0;
}

解密出的 xxx_Decrypt.db 文件可通过 Navicat Premium 数据库可视化工具查看。

JavaScript 常用继承方式总结

基于prototype方式

function Super(){
    this.val = 1;
    this.arr = [];
}
function Sub(){
    // ...
}
Sub.prototype = new Super();   

var sub1 = new Sub();
var sub2 = new Sub();

sub1.val = 2;
sub1.arr.push(2);

alert(sub1.val);    // 2
alert(sub2.val);    // 1

alert(sub1.arr);    // 2
alert(sub2.arr);    // 2

优缺点

  • 简单,易于实现

  • 原型对象的引用属性是所有实例共享

执行sub1.arr.push(2);先对sub1进行属性查找,找遍了实例属性(在本例中没有实例属性),没找到,就开始顺着原型链向上找,拿到了sub1的原型对象,发现有arr属性。于是给arr末尾插入了2,所以sub2.arr也变了
创建子类实例时,无法向父类构造函数传参

借用构造函数

function Super(val){
    this.val = val;
    this.arr = [];
    this.say = function() { alert(1) }
}
function Sub(val){
    Super.call(this, val); 
    // ...
}

var sub1 = new Sub(1);
var sub2 = new Sub(2);
sub1.arr.push(2);
alert(sub1.val);    // 1
alert(sub2.val);    // 2

alert(sub1.arr);    // [2]
alert(sub2.arr);    // []

alert(sub1.say === sub2.say);   // false

优缺点

  • 解决了子类实例共享父类引用属性的问题
  • 创建子类实例时,可以向父类构造函数传参
  • 无法实现函数复用,每个子类实例都持有一个新的say函数,会影响性能,内存增加

组合继承

function Super(){
    this.val = 1;
    this.arr = [];
}
//  在此处声明函数
Super.prototype.fun1 = function(){};
Super.prototype.fun2 = function(){};

function Sub(){
    Super.call(this);   
    // ...
}
Sub.prototype = new Super();   

var sub1 = new Sub(1);
var sub2 = new Sub(2);
alert(sub1.fun === sub2.fun);   // true

优缺点

  • 不存在引用属性共享问题
  • 可传参
  • 函数可复用
  • 父类构造函数被调用了两次,子类原型上有一份多余的父类实例属性

寄生组合继承

function create(obj) {   
    var F = function(){};
    F.prototype = obj;
    return new F();
}

function Super(){
    this.val = 1;
    this.arr = [1];
}

Super.prototype.fun1 = function(){};
Super.prototype.fun2 = function(){};

function Sub(){
    Super.call(this);  
    // ...
}
var proto = create(Super.prototype); 
proto.constructor = Sub;             
Sub.prototype = proto;               

var sub = new Sub();
alert(sub.val);
alert(sub.arr);

优缺点

  • 最佳方式

原型式继承

function create(obj){  
    var F = function(){};
    F.prototype = obj;
    return new F();
}
function Super(){
    this.val = 1;
    this.arr = [];
}

// 拿到父类对象
var sup = new Super();

var sub = create(sup); 
// 增强
sub.attr1 = 1;
sub.attr2 = 2;
//sub.attr3...

alert(sub.val);     // 1
alert(sub.arr);     // 1
alert(sub.attr1);   // 1

优缺点

  • create函数得到得到一个“纯洁”的新对象(“纯洁”是因为没有实例属性),再逐步增强之(填充实例属性), ES5提供了Object.create()函数,内部就是原型式继承
  • 无法实现函数复用
  • 原型引用属性会被所有实例共享

Spring Boot cache with Redis

Cache abstraction

Spring Framework provides an abstraction layer with set of annotations for caching support and can work together with various cache implementation like Redis, EhCache, Hazelcast, Infinispan and many more.

@Cacheable

Fulfill cache after method execution, next invocation with the same arguments will be omitted and result will be loaded from cache. Annotation provide useful feature called conditional caching. In some case no all data should be cached e.g. you want store in memory only most popular posts.

@Cacheable(value = "post-single", key = "#id", unless = "#result.shares < 500")
@GetMapping("/{id}")
public Post getPostByID(@PathVariable String id) throws PostNotFoundException {
    log.info("get post with id {}", id);
    return postService.getPostByID(id);
}
@Cacheable(value = "post-top")
@GetMapping("/top")
public List<Post> getTopPosts() {
    return postService.getTopPosts();
}

@CachePut

Annotation allows to update entry in cache and support same options like Cacheable annotation. Code below updates post and return it for cache provider to change entry with new value.

@CachePut(value = "post-single", key = "#post.id")
@PutMapping("/update")
public Post updatePostByID(@RequestBody Post post) throws PostNotFoundException {
    log.info("update post with id {}", post.getId());
    postService.updatePost(post);
    return post;
}

@CacheEvict

Remove entry from cache, can be both conditional and global to all entries in specific cache.

@CacheEvict(value = "post-single", key = "#id")
@DeleteMapping("/delete/{id}")
public void deletePostByID(@PathVariable String id) throws PostNotFoundException {
    log.info("delete post with id {}", id);
    postService.deletePost(id);
}

@CacheEvict(value = "post-top")
@GetMapping("/top/evict")
public void evictTopPosts() {
    log.info("Evict post-top");
}

@EnableCaching

Annotation ensure that post processor will check all beans trying to find demarcated methods and will create proxy to intercept all invocations.

@caching

Aggregate multiple annotations of the same type when e.g. you need to use different conditions and caches.

@CacheConfig

Class level annotation allows to specify global values for annotations like cache name or key generator.

Hibernate Second-Level Cache

One of the advantages of database abstraction layers such as ORM (object-relational mapping) frameworks is their ability to transparently cache data retrieved from the underlying store. This helps eliminate database-access costs for frequently accessed data.

What Is a Second-Level Cache?

As most other fully-equipped ORM frameworks, Hibernate has the concept of first-level cache. It is a session scoped cache which ensures that each entity instance is loaded only once in the persistent context.

Once the session is closed, first-level cache is terminated as well. This is actually desirable, as it allows for concurrent sessions to work with entity instances in isolation from each other.

On the other hand, second-level cache is SessionFactory-scoped, meaning it is shared by all sessions created with the same session factory. When an entity instance is looked up by its id (either by application logic or by Hibernate internally, e.g. when it loads associations to that entity from other entities), and if second-level caching is enabled for that entity, the following happens:

  • If an instance is already present in the first-level cache, it is returned from there
  • If an instance is not found in the first-level cache, and the corresponding instance state is cached in the second-level cache, then the data is fetched from there and an instance is assembled and returned
  • Otherwise, the necessary data are loaded from the database and an instance is assembled and returned

Once the instance is stored in the persistence context (first-level cache), it is returned from there in all subsequent calls within the same session until the session is closed or the instance is manually evicted from the persistence context. Also, the loaded instance state is stored in L2 cache if it was not there already.

Spring Boot cache with Redis

Hibernate Second-Level Cache

spring-cache-tutorial

VirtualBox 虚拟机的几种网络模式

VirtualBox网络连接方式概要

  1. NAT 网络地址转换模式(NAT,Network Address Translation)
  2. Bridged Adapter 桥接模式
  3. Internal 内部网络模式
  4. Host-only Adapter 主机模式

VirturalBox为每个虚拟机提供八种虚拟的PCI 网卡,对于每一种虚拟网卡,你可以从下列六种网络硬件中任选一种:

        AMD PCNet PCI II (Am79C970A)
        AMD PCNet FAST III (Am79C973, the default)
        Intel PRO/1000 MT Desktop (82540EM)(Windows Vista and later versions)
        Intel PRO/1000 T Server (82543GC)(Windows XP)
        Intel PRO/1000 MT Server (82545EM)(OVF imports from other platforms)
        Paravirtualized network adapter (virtio-net)

NAT模式

特点:

  • 虚拟机与主机关系: 只能单向访问,虚拟机可以通过网络访问到主机,主机无法通过网络访问到虚拟机。
  • 虚拟机可以ping通主机(此时ping虚拟机的网关,即是ping主机)
  • 虚拟机与网络中其他主机的关系: 只能单向访问,虚拟机可以访问到网络中其他主机,其他主机不能通过网络访问到虚拟机。
  • 虚拟机与虚拟机之间的关系: 相互不能访问,虚拟机与虚拟机各自完全独立,相互间无法通过网络访问彼此。

应用场景:

  • 虚拟机只要求可以上网,无其它特殊要求,满足最一般需求

配置方法:

        连接方式 选择 网络地址转换(NAT)
        高级-控制芯片 选择 PCnet-FAST III
        高级-混杂模式 拒绝
        高级-接入网线 √   (虚拟机ip自动获取)

        ip样式:
        ip 10.0.2.15
        网关 10.0.2.2
        DNS    10.0.2.3
  • 注意此处的网关在不同虚拟机中可能是同一个值,但是这归属于不同的NAT Engine,因此实际上各个虚拟机用的不是同一个网关

原理:

  • 虚拟机的请求传递给NAT Engine,由它来利用主机进行对外的网络访问,返回的数据包再由NAT Engine给虚拟机。

Bridged Adapter模式(桥接模式)

特点:

  • 虚拟机与主机关系: 可以相互访问,因为虚拟机在真实网络段中有独立IP,主机与虚拟机处于同一网络段中,彼此可以通过各自IP相互访问。
  • 虚拟机于网络中其他主机关系:以相互访问,同样因为虚拟机在真实网络段中有独立IP, 虚拟机与所有网络其他主机处于同一网络段中,彼此可以通过各自IP相互访问。
  • 虚拟机于虚拟机关系: 可以相互访问,原因同上。

应用场景:

  • 虚拟机要求可以上网,且虚拟机完全模拟一台实体机

配置方法:

        连接方式 选择 桥接网卡
        界面名称 选择 (如果你的笔记本有无线网卡和有线网卡,需要根据现在的上网方式对应选择)
        高级-控制芯片 选择 PCnet-FAST III
        高级-混杂模式 拒绝
        高级-接入网线 √(虚拟机ip自动获取)

原理:

  • 通过主机网卡,架设一条桥,直接连入到网络中。它使得虚拟机能被分配到一个网络中独立的IP,所有网络功能完全和
  • 在网络中的真实机器一样。 (虚拟机是通过主机所在网络中的DHCP服务得到ip地址的,所以按理来说,两者是完全独立的,但事实却是虚拟机是没有独立硬件的,它还是要依靠主机的网卡,因此,主机要断开网络,虚拟机也就没法拿到ip了

Host-only Adapter模式

特点:

  • 虚拟机与主机关系 :默认不能相互访问,双方不属于同一IP段,host-only网卡默认IP段为192.168.56.X 子网掩码为255.255.255.0,后面的虚拟机被分配到的也都是这个网段。通过网卡共享、网卡桥接等,可以实现虚拟机于主机相互访问。
  • 虚拟机访问主机: 用的是主机的VirtualBox Host-Only Network网卡的IP:192.168.56.1 ,不管主机“本地连接”有无红叉,永远通。(注意虚拟机与主机通信是通过主机的名为VirtualBox Host-Only Network的网卡,因此ip是该网卡ip 192.168.56.1,而不是你现在正在上网所用的ip)
  • 主机访问虚拟机,用是的虚拟机的网卡的IP: 192.168.56.101 ,不管主机“本地连接”有无红叉,永远通。
  • 主机可以访问主机下的所有虚拟机,和192.168.56.1(是VirtualBox Host-Only Network网卡[在主机中模拟出的网卡,不是虚拟机中虚拟的网卡]的IP)
  • 虚拟机与网络主机关系 :默认不能相互访问,也不能上网,原因同上,通过设置,可以实现相互访问。
  • 虚拟机与虚拟机关系 :默认可以相互访问,都是同处于一个网段。

应用场景:

  • 在主机无法上网的情况下(主机可以上网的情况下可以用host-only,也可以用桥接),需要搭建一个模拟局域网,所有机器可以互访

配置方法:

 连接方式 选择 仅主机(Host-Only)适配器
        界面名称 选择 VirtualBox Host-Only Ethernet Adapter
            如果无法设置界面名称,可以:In VirtualBox > Preferences > Network, set up a host-only network
        高级-控制芯片 选择 PCnet-FAST III
        高级-混杂模式 拒绝
        高级-接入网线 √
        (虚拟机ip自动获取,也可以自己进行配置,网关配置为主机中虚拟网卡的地址【默认为192.168.56.1】,ip配置为与虚拟网卡地址同网段地址)

Koa 搭建API server 时加入JWT做用户授权

koa

Koa 是NodeJS面向下一代的服务器框架,下面主要介绍如何使用JWT(json web token) 保护私有的API

什么是 JWT?

JWT 代表JSON web token。 它是一种用于交换安全信息的标准令牌,主要用于身份验证。 JSON web token 体积非常的小,它可以通过下面几种方式传输:

  • 通过URL
  • 放在POST 的参数中
  • 放在HTTP header中

Token 由三部分构成,以 . 作为分割符号

  • HEADER : 包含使用的散列算法和作为JWT的令牌的类型
  • PAYLOAD : 您想要与凭证交换的信息。 例如在认证令牌的情况下,可以是用户ID及role
  • SIGNATURE: 用于验证令牌的真实性。 SIGNATURE 是使用HEADERPAYLOADSECRET KEY来生成, 因此它可以用于检查PAYLOAD没有被改变。

JWT的格式一般是这样: <header>.<payload>.<signature>

在使用身份认证的场景里,JWT 一般以下面的流程进行的

登录授权部分

  • 用户发送登录请求,发送身份信息到到服务器鉴权
  • 服务器判断用户信息是否合法
  • 服务器生成一个TOKEN, 里面包含一些用户相关的信息,用于以后识别该用户
  • 服务器对TOKEN进行签名
  • 服务器发送TOKEN给用户
  • 用户将TOKEN 保存到Cookie 或 Local storage 中

用户已授权后的请求场景

  • 用户将TOKEN放到请求的header中,以便服务器能识别当前用户的身份
  • 服务器拿到请求头部的TOKEN后,decode一次,再提取payload中的信息,识别出该用户
  • 服务器进行以后的相关业务逻辑

用户无法改变PLAYLOAD里面的数据来伪造其他用户的信息,因为PLAYLOAD也是用于生产签名的factor之一,修改后服务器认证将无法通过

JWT的身份验证机制的主要优点就是不需要依赖于SESSION

TOKEN像访问卡,它是存储的就是它用户的信息,通常在cookie或local storage。后端不保存已经签发过的TOKEN。

SECRET KEY 不与用户共享,它只存储在服务器上,这使得它不太可能被黑客破解。

JWT 也比其他SAML令牌体积要小。这使它更适合于在HTML和HTTP环境中传递。

JWT 一个主要缺点是,当恶意用户可以拿到别人的一个有效的令牌后,可以直接用别人的TOKEN作为请求,这就是为什么要使用SSL来保护在每个请求上发送的令牌的重要原因。

swift4 语法备忘- Enumeration

Enumerations

An enumeration defines a common type for a group of related values and enables you to work with those values in a type-safe way within your code.

C enumerations assign related names to a set of integer values. Enumerations in Swift do not have to provide a value for each case of the enumeration.

If a raw value is provided for each enumeration case, the value can be a string , a character , integer or float.

First-class types

They adopt many features traditionally supported only by classes

  • Computed properties to provide additional information about the enumeration’s current value,
  • Instance methods to provide functionality related to the values the enumeration represents.
  • Define initializers to provide an initial case value
  • Expand their functionality beyond their original implementation;
  • Conform to protocols to provide standard functionality.

Systax

enum SomeEnumeration {
    // enumeration definition goes here
}

enum CompassPoint {
    case north
    case south
    case east
    case west
}

// cases can appear on a single line, separated by commas:

enum CompassPoint {
  case north, south, east, west
}

Once directionToHead is declared as a CompassPoint , you can set it to a different CompassPoint value using a shorter dot syntax:

var toward: CompassPoint = .west;

switch toward {
	case .west, .south, .east, .north: 
       print("west direction") 
	default:
       print("Not a direction")
}

Associated value

Store associated values of other types alongside these case values.

enum Barcode {
    case upc(Int, Int, Int, Int)
    case qrCode(String)
}
var productBarcode = Barcode.upc(8, 85909, 51226, 3)
var productQRcode = .qrCode("ABCDEFGHIJKLMNOP")

switch productBarcode {
case .upc(let numberSystem, let manufacturer, let product, let check):
    print("UPC: \(numberSystem), \(manufacturer), \(product), \(check).")
case .qrCode(let productCode):
    print("QR code: \(productCode).")
}

Raw Values

Cases can come prepopulated with default values (raw values ), which are all of the same type.

// Explicitly assign 
enum ASCIIControlCharacter: Character {
    case tab = "\t"
    case lineFeed = "\n"
    case carriageReturn = "\r"
}

// Implicitly assign
enum Planet: Int {
    case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
}

enum CompassPoint: String {
    case north, south, east, west
}

let earthsOrder = Planet.earth.rawValue
// earthsOrder is 3
 
let sunsetDirection = CompassPoint.west.rawValue
// sunsetDirection is "west"

Initializing from a Raw Value

Enumeration initializer that takes a value of the raw value’s type (as a parameter called rawValue) and returns either an enumeration case or nil. You can use this initializer to try to create a new instance of the enumeration.

let possiblePlanet = Planet(rawValue: 7)
// possiblePlanet is of type Planet? and equals Planet.uranus

let positionToFind = 11

if let somePlanet = Planet(rawValue: positionToFind) {
    switch somePlanet {
    case .earth:
        print("Mostly harmless")
    default:
        print("Not a safe place for humans")
    }
} else {
    print("There isn't a planet at position \(positionToFind)")
}
// Prints "There isn't a planet at position 11"

Webpack 2 升级指南和特性

resolve.root, resolve.fallback, resolve.modulesDirectories

上述三个选项将被合并为一个标准配置项:resolve.modules. 更多关于resolve的信息信息可查阅 resolving.

  resolve: {
-   root: path.join(__dirname, "src")
+   modules: [
+     path.join(__dirname, "src"),
+     "node_modules"
+   ]
  }

resolve.extensions

该配置项将不再要求强制转入一个空字符串,而被改动到了resolve.enforceExtension下, 更多关于resolve的信息信息可查阅 resolving.

resolve.*

更多相关改动和一些不常用的配置项在此不一一列举,大家如果在实际项目中用到可以到resolving中进行查看.

module.loaders 将变为 module.rules

旧版本中loaders配置项将被功能更为强大的rules取代,同时考虑到新旧版本的兼容,之前旧版本的module.loaders的相关写法仍旧有效,loaders中的相关配置项也依旧可以被识别。

新的loader配置规则会变得更加通俗易用,因此官方也非常推荐用户能及时按module.rules中的相关配置进行调整升级。

  module: {
-   loaders: [
+   rules: [
      {
        test: /\.css$/,
-       loaders: [
+       use: [
          {
            loader: "style-loader"
          },
          {
            loader: "css-loader",
-           query: {
+           options: {
              modules: true
            }
          }
        ]
      },
      {
        test: /\.jsx$/,
        loader: "babel-loader", // Do not use "use" here
        options: {
          // ...
        }
      }
    ]
  }

链式loaders

同webpack1.X中类似,loaders继续支持链式写法,可将相关正则匹配到的文件资源数据在几个loader之间进行共享传递,详细使用说明可见 rule.use

在wepback2中,用户可通过use项来指定需要用到的loaders列表(官方推荐),而在weback1中,如果需要配置多个loaders则需要依靠简单的 !符来切分,这种语法出于新旧兼容的考虑,只会在module.loaders中生效。

  module: {
-   loaders: {
+   rules: {
      test: /\.less$/,
-     loader: "style-loader!css-loader!less-loader"
+     use: [
+       "style-loader",
+       "css-loader",
+       "less-loader"
+     ]
    }
  }

##module名称后自动自动补全 -loader的功能将被移除

在配置loader时,官方不再允许省略-loader扩展名,loader的配置写法上将逐步趋于严谨。

  module: {
    rules: [
      {
        use: [
-         "style",
+         "style-loader",
-         "css",
+         "css-loader",
-         "less",
+         "less-loader",
        ]
      }
    ]
  }

当然,如果你想继续保持之前的省略写法,你写可以在resolveLoader.moduleExtensions中开启默认扩展名配置,不过这种做法并不被推荐。

+ resolveLoader: {
+   moduleExtensions: ["-loader"]
+ }

可以从这里查看 #2986此次变更的原因;

json-loader无需要独立安装

当我们需要读取json格式文件时,我们不再需要安装任何loader,webpack2中将会内置 json-loader,自动支持json格式的读取(喜大普奔啊)。

  module: {
    rules: [
-     {
-       test: /\.json/,
-       loader: "json-loader"
-     }
    ]
  }

为何需要默认支持json格式官方的解释是为了在webpack, node.js and browserify三种构建环境下提供无差异的开发体验。

loader配置项将默认从context中读取

在webpack 1中的一些特殊的loader在读取对应资源时,需要通过require.resolve指定后才能指定生效。从webpack 2后,配置loader在直接从context中进行读取,这就解决了一些在使用“npm链接”或引用模块之外的context造成的模块重复导入的问题。

配置中可以删除如下代码:

  module: {
    rules: [
      {
        // ...
-       loader: require.resolve("my-loader")
+       loader: "my-loader"
      }
    ]
  },
  resolveLoader: {
-   root: path.resolve(__dirname, "node_modules")
  }

module.preLoadersmodule.postLoaders 将被移除

  module: {
-   preLoaders: [
+   rules: [
      {
        test: /\.js$/,
+       enforce: "pre",
        loader: "eslint-loader"
      }
    ]
  }

之前需要用到preLoader的地方可以改到rules的enfore中进行配置。

UglifyJsPlugin中的 sourceMap配置项将默认关闭

UglifyJsPlugin中的sourceMap 默认项将从 true变为 false

这就意味着当你的js编译压缩后,需要继续读取原始脚本信息的行数,位置,警告等有效调试信息时,你需要手动开启UglifyJsPlugin 的配置项:sourceMap: true

  devtool: "source-map",
  plugins: [
    new UglifyJsPlugin({
+     sourceMap: true
    })
  ]

UglifyJsPlugin 的警告配置将默认关闭

UglifyJsPlugin中的 compress.warnings 默认项将从 true变为 false

这就意味着当你想在编译压缩的时候查看一部分js的警告信息时,你需要将compress.warnings 手动设置为 true

  devtool: "source-map",
  plugins: [
    new UglifyJsPlugin({
+     compress: {
+       warnings: true
+     }
    })
  ]

UglifyJsPlugin 不再支持让 Loaders 最小化文件的模式了

UglifyJsPlugin 将不再支持让 Loaders 最小化文件的模式。debug 选项已经被移除。Loaders 不能从 webpack 的配置中读取到他们的配置项。

loade的最小化文件模式将会在webpack 3或者后续版本中被彻底取消掉.

为了兼容部分旧式loader,你可以通过 LoaderOptionsPlugin 的配置项来提供这些功能。

  plugins: [
+   new webpack.LoaderOptionsPlugin({
+     minimize: true
+   })
  ]

DedupePlugin 已经被移除

webpack.optimize.DedupePlugin 不再需要. 从你以前的配置移除这个配置选项.

BannerPlugin 配置项将有所改变

BannerPlugin 将不再允许接受两个参数,而是只提供一个对象配置项.

  plugins: [
-    new webpack.BannerPlugin('Banner', {raw: true, entryOnly: true});
+    new webpack.BannerPlugin({banner: 'Banner', raw: true, entryOnly: true});
  ]

OccurrenceOrderPlugin 将被内置加入

不需要再针对OccurrenceOrderPlugin进行配置

  plugins: [
-   new webpack.optimize.OccurrenceOrderPlugin()
  ]

ExtractTextWebpackPlugin配置项将有所改变

ExtractTextPlugin ] 1.0.0 在webpack v2将无法使用,你需要重新指定安装ExtractTextPlugin 的webpack2的适配版本.

npm install --save-dev extract-text-webpack-plugin@beta

更新后的ExtractTextPlugin版本会针对wepback2进行相应的调整。

ExtractTextPlugin.extract的配置书写方式将调整

module: {
  rules: [
    test: /.css$/,
-    loader: ExtractTextPlugin.extract("style-loader", "css-loader", { publicPath: "/dist" })
+    loader: ExtractTextPlugin.extract({
+      fallbackLoader: "style-loader",
+      loader: "css-loader",
+      publicPath: "/dist"
+    })
  ]
}

new ExtractTextPlugin({options})的配置书写方式将调整

plugins: [
-  new ExtractTextPlugin("bundle.css", { allChunks: true, disable: false })
+  new ExtractTextPlugin({
+    filename: "bundle.css",
+    disable: false,
+    allChunks: true
+  })
]

全量动态加载资源将默认失效

只有使用一个表达式的资源依赖引用(i. e. require(expr)),现在将创建一个空的context,而不是一个context的完整目录。

当在es2015的模块化中无法工作时,请最好重构这部分的代码,如果无法进行修改这部分代码,你可以在ContextReplacementPlugin中来提示编译器做出正确处理。

###Cli使用自定义参数作为配置项传入方式将做调整

如果你随意将自定义参数通过cli传入到配置项中,如:

webpack --custom-stuff

// webpack.config.js
var customStuff = process.argv.indexOf("--custom-stuff") >= 0;
/* ... */
module.exports = config;

你会发现这将不会被允许,cli的执行将会遵循更为严格的标准。

取而代之的是用一个接口来做传递参数配置。这应该是新的代替方案,未来的工具开发也可能依赖于此。

webpack --env.customStuff

module.exports = function(env) {
  var customStuff = env.customStuff;
  /* ... */
  return config;
};

查看更多介绍 CLI.

##require.ensureAMD require将采用异步式调用

require.ensureamd require将默认采用异步的加载方式来调用,而非之前的当模块请求加载完成后再在回调函数中同步触发。

require.ensure将基于原生的Promise对象重新实现,当你在使用 require.ensure 时请确保你的运行环境默认支持Promise对象,如果缺少则推荐使用安装polyfill.

Loader的配置项将通过options来设置

webpack.config.js中将不再允许使用自定义属性来配置loder,这直接带来的一个影响是:在ts配置项中的自定义属性将无法在被在webpack2中正确使用:

module.exports = {
  ...
  module: {
    rules: [{
      test: /\.tsx?$/,
      loader: 'ts-loader'
    }]
  },
  // does not work with webpack 2
  ts: { transpileOnly: false }
}

什么是 options?

这是一个非常好的提问,严格意义上来说,custom propertyoptions均是用于webpack loader的配置方式,从更通俗的说法上看,options应该被称作query,作为一种类似字符串的形式被追加到每一个loader的命名后面,非常类似我们用于url中的查询字符串,但在实际应用中功能要更为强大:

module.exports = {
  ...
  module: {
    rules: [{
      test: /\.tsx?$/,
      loader: 'ts-loader?' + JSON.stringify({ transpileOnly: false })
    }]
  }
}

options也可作为一个独立的字面对象量,在loader的配置中搭配使用。

module.exports = {
  ...
  module: {
    rules: [{
      test: /\.tsx?$/,
      loader: 'ts-loader',
      options:  { transpileOnly: false }
    }]
  }
}

LoaderOptionsPlugin context

部分loader需要配置context信息, 并且支持从配置文件中读取。这需要loader通过用长选项传递进来,更多loader的明细配置项可以查阅相关文档。

为了兼容部分旧式的loader配置,也可以采用如下插件的形式来进行配置:

  plugins: [
+   new webpack.LoaderOptionsPlugin({
+     options: {
+       context: __dirname
+     }
+   })
  ]

debug

debug作为loader中的一个调试模式选项,可以在webpack1的配置中灵活切换。在webpack2中,则需要loader通过用长选项传递进来,更多loader的明细配置项可以查阅相关文档。

loder的debug模式在webpack3.0或者后续版本中将会被移除。

为了兼容部分旧式的loader配置,也可以采用如下插件的形式来进行配置:

- debug: true,
  plugins: [
+   new webpack.LoaderOptionsPlugin({
+     debug: true
+   })
  ]

Code Splitting with ES2015

在webpack1中,你需要使用require.ensure实现chunks的懒加载,如:

require.ensure([], function(require) {
  var foo = require("./module");
});

在es2015的 loader中通过定义import()作为资源加载方法,当读取到符合ES2015规范的模块时,可实现模块中的内容在运行时动态加载。

webpack在处理import()时可以实现按需提取开发中所用到的模块资源,再写入到各个独立的chunk中。webpack2已经支持原生的 ES6 的模块加载器了,这意味着 webpack 2 能够理解和处理 importexport了。

import()支持将模块名作为参数出入并且返回一个Promise对象。

function onClick() {
  import("./module").then(module => {
    return module.default;
  }).catch(err => {
    console.log("Chunk loading failed");
  });
}

这样做的还有一个额外的好处就是当我们的模块加载失败时也可以被捕获到了,因为这些都会遵循Promise的标准来实现。

值得注意的地方:require.ensure的第三个参数选项允许使用简单的chunk命名方式,但是import API中将不被支持,如果你希望继续采用函数式的写法,你可以继续使用require.ensure

require.ensure([], function(require) {
  var foo = require("./module");
}, "custom-chunk-name");

(注: System.import将会被弃用,webpack中将不再推荐使用 System.import,官方也推荐使用import进行替换,详见 v2.1.0-beta.28)

如果想要继续使用Babel中提供的import,你需要独立安装 dynamic-import 插件并且选择babel的Stage 3来捕获时的错误, 当然这也可以根据实际情况来操作而不做强制约束。

Dynamic expressions动态表达式

现在import()中的传参可支持部分表达式的写法了,如果之前有接触过CommonJS中require()表达式写法,应该不会对此感到陌生,(它的操作其实和 CommonJS 是类似的,给所有可能的文件创建一个环境,当你传递那部分代码的模块还不确定的时候,webpack 会自动生成所有可能的模块,然后根据需求加载。这个特性在前端路由的时候很有用,可以实现按需加载资源)

import() 会针对每一个读取到的module创建独立的separte chunk

function route(path, query) {
  return import(`./routes/${path}/route`)
    .then(route => new route.Route(query));
}
// This creates a separate chunk for each possible route

可以混用 ES2015 和 AMD 和 CommonJS

在 AMD 和 CommonJS 模块加载器中,你可以混合使用所有(三种)的模块类型(即使是在同一个文件里面)。

// CommonJS consuming ES2015 Module
var book = require("./book");

book.currentPage;
book.readPage();
book.default === "This is a book";
// ES2015 Module consuming CommonJS
import fs from "fs"; // module.exports map to default
import { readFileSync } from "fs"; // named exports are read from returned object+

typeof fs.readFileSync === "function";
typeof readFileSync === "function";

注:es2015 balel 的默认预处理会把 ES6 模块加载器转化成 CommonJS 模块加载。要是想使用 webpack 新增的对原生 ES6 模块加载器的支持,你需要使用 es2015-webpack 来代替,另外如果你希望继续使用babel,则需要通过配置babel项,使其不会强制解析这部分的module symbols以便webpack能正确使用它们,babel的配置如下:

.babelrc

{
  "presets": [
    ["es2015", { "modules": false }]
  ]
}

Hints

No need to change something, but opportunities

Template strings模板字符串

webpack中的资源参数已经开始支持模板字符串了,这意味着你可以使用如下的配置写法:

- require("./templates/" + name);
+ require(`./templates/${name}`);

###配置支持项支持Promise

webpack现在在配置文件项中返回Promise了,这就允许你在配置中可以进行一些异步的写法了,如下所示:

webpack.config.js

module.exports = function() {
  return fetchLangs().then(lang => ({
    entry: "...",
    // ...
    plugins: [
      new DefinePlugin({ LANGUAGE: lang })
    ]
  }));
};

Loader匹配支持更多的高级写法

webpack中的loader配置支持如下写法:

module: {
  rules: [
    {
      resource: /filename/, // matches "/path/filename.js"
      resourceQuery: /querystring/, // matches "/filename.js?querystring"
      issuer: /filename/, // matches "/path/something.js" if requested from "/path/filename.js"
    }
  ]
}

更多的CLI参数项

如下有更多的CLI 参数项可用:

--define process.env.NODE_ENV="production" 支持直接配置DefinePlugin.

--display-depth 能显示每个entry中的module的资源深度

--display-used-exports 能显示每个module中依赖使用了哪些资源.

--display-max-modules能限制显示output中引用到的资源数量 (默认显示15个).

-p 指定当前的编译环境为生产环境,即修改:process.env.NODE_ENV"production"

Cacheable缓存项

Loaders现在将默认开启资源缓存了,如果你不希望loader读缓存则需要在配置中指明:

  // Cacheable loader
  module.exports = function(source) {
-   this.cacheable();
    return source;
  }
  // Not cacheable loader
  module.exports = function(source) {
+   this.cacheable(false);
    return source;
  }

Complex options复合参数项写法

webpack1中的loader参数项中只支持JSON.stringify-able这种json字符串的写法;

webpack2中的loader参数项中已经可以支持任意的JS对象的写法了。

使用复合选项时会有一个限制,你需要配置一个ident作为项来保证能正确引用到其他的loader,这意味着通过配置我们可以在内联写法中去调用对应依赖的加载器,如下:

require("some-loader??by-ident!resource")

{
  test: /.../,
  loader: "...",
  options: {
    ident: "by-ident",
    magic: () => return Math.random()
  }
}

这种写法在平常开发中用的不算多,但是有一种场景下会比较有用,就是当我们的loader需要去生成独立的代码片段时,如,我们在使用style-loader生成一个模块时,需要依赖前面的loader计算的结果。

// style-loader generated code (simplified)
var addStyle = require("./add-style");
var css = require("-!css-loader?{"modules":true}!postcss-loader??postcss-ident");

addStyle(css);

在这种复杂选项的使用时ident就有用武之地了。

结尾

webpack2无论是从优化资源配置项,到向es6 module,Promise等新标准接轨,再到编译环境和性能的优化,再到API设计的整体规范性上,相对V1的改进还是非常显著的,希望大家多多尝试,及时反馈交流,让webapck的生态圈变得日益活跃强大。

使用power-assert代替should/expect/chai

在写node的单元测试的时候我们常用的断言库有

user.should.have.property('name', 'test');
user.enabled.should.ok;

expect(5).to.be.a('number');
expect(res.body.data).to.be.deep.equal({});

存在的问题

  • API种类繁多,需要经常翻阅文档
  • 出错时反馈的信息无法准确判断引起的原因,很多时候需要配合log,或者debug的工具

Power Assert in JavaScript

Power Assert in JavaScript.

Provides descriptive assertion messages through standard assert interface.

No API is the best API.

https://github.com/power-assert-js/power-assert

和常用的断言库相比,它的优点是

  • API 不需要记忆
  • 错误反馈信息非常详尽
const assert = require('power-assert');

describe('test/showcase.test.js', () => {
  const arr = [ 1, 2, 3 ];

  it('power-assert', () => {
    assert(arr[1] === 10);
  });
});

// output:
4) test/showcase.test.js power-assert:

      AssertionError:   # test/showcase.test.js:6

  assert(arr[1] === 10)
         |  |   |
         |  2   false
         [1,2,3]

  [number] 10
  => 10
  [number] arr[1]
  => 2
var assert = require('assert');

describe('Array', function(){
    beforeEach(function(){
        this.ary = [1,2,3];
    });
    describe('#indexOf()', function(){
        it('should return index when the value is present', function(){
            var zero = 0, two = 2;
            assert(this.ary.indexOf(zero) === two);
        });
        it('should return -1 when the value is not present', function(){
            var minusOne = -1, two = 2;
            assert.ok(this.ary.indexOf(two) === minusOne, 'THIS IS AN ASSERTION MESSAGE');
        });
    });
});

describe('various types', function(){
    function Person(name, age) {
        this.name = name;
        this.age = age;
    }
    beforeEach(function(){
        this.types = [
            'string', 98.6, true, false, null, undefined,
            ['nested', 'array'],
            {object: true},
            NaN, Infinity,
            /^not/,
            new Person('alice', 3)
        ];
    });
    it('demo', function(){
        var index = this.types.length -1,
            bob = new Person('bob', 5);
        assert(this.types[index].name === bob.name);
    });
});

HOW TO USE

使用 babel-plugin-espower 插件

安装

npm install --save-dev babel-plugin-espower

编辑 .babelrc 文件

{
  "presets": [
    ...
  ],
  "plugins": [
    "babel-plugin-espower"
  ]
}

mocha 运行时通过 babel-register 编译文件

$(npm bin)/mocha --require babel-register test/some_test.js

electron-builder 使用备忘

Electron-builder打包详解

开发electron客户端程序,打包是绕不开的问题。下面就我在工作中的经验以及目前对electron-builder的了解来分享一些心得。

基本概念

官网的定义

A complete solution to package and build a ready for distribution Electron app for macOS, Windows and Linux with “auto update” support out of the box.
关于electronelectron-builder的基础部分这篇文章就跳过了,有兴趣的话可以看这篇文章

如何使用

builder的使用和配置都是很简单的
builder配置有两种方式

  • package.json中直接配置使用(比较常用,我们下面着重来讲这个)
  • 指定electron-builder.yml文件

demo地址会在文章末尾给出(demo项目中electron使用得是V2.0.7版本,目前更高得是2.0.8版本)。

下面是一个简单的package.js中带注释的配置

  1. 基础配置
"build": {  // 这里是electron-builder的配置
    "productName":"xxxx",//项目名 这也是生成的exe文件的前缀名
    "appId": "com.xxx.xxxxx",//包名  
    "copyright":"xxxx",//版权  信息
    "directories": { // 输出文件夹
      "output": "build"
    }, 
    // windows相关的配置
    "win": {  
      "icon": "xxx/icon.ico"//图标路径 
    }  
  }

在配置文件中加入以上的文件之后就可以打包出来简单的文件夹,文件夹肯定不是我们想要的东西。下一步我们来继续讲别的配置。
2. 打包目标配置
要打包成安装程序的话我们有两种方式,

  1. 使用NSIS工具对我们的文件夹再进行一次打包,打包成exe
  2. 通过electron-builder的nsis直接打包成exe,配置如下
"win": {  // 更改build下选项
    "icon": "build/icons/aims.ico",
    "target": [
      {
        "target": "nsis" // 我们要的目标安装包
      }
    ]
  },
  1. 其他平台配置
  "dmg": { // macOSdmg
    "contents": [
      ...
    ]
    },
    "mac": {  // mac
      "icon": "build/icons/icon.icns"
    },
    "linux": { // linux
      "icon": "build/icons"
    }
  1. nsis配置

这个要详细的讲一下,这个nsis的配置指的是安装过程的配置,其实还是很重要的,如果不配置nsis那么应用程序就会自动的安装在C盘。没有用户选择的余地,这样肯定是不行的

关于nsis的配置是在build中nsis这个选项中进行配置,下面是部分nsis配置

"nsis": {
  "oneClick": false, // 是否一键安装
  "allowElevation": true, // 允许请求提升。 如果为false,则用户必须使用提升的权限重新启动安装程序。
  "allowToChangeInstallationDirectory": true, // 允许修改安装目录
  "installerIcon": "./build/icons/aaa.ico",// 安装图标
  "uninstallerIcon": "./build/icons/bbb.ico",//卸载图标
  "installerHeaderIcon": "./build/icons/aaa.ico", // 安装时头部图标
  "createDesktopShortcut": true, // 创建桌面图标
  "createStartMenuShortcut": true,// 创建开始菜单图标
  "shortcutName": "xxxx", // 图标名称
  "include": "build/script/installer.nsh", // 包含的自定义nsis脚本 这个对于构建需求严格得安装过程相当有用。
},

关于includescript 到底选择哪一个 ?

在对个性化安装过程需求并不复杂,只是需要修改一下安装位置,卸载提示等等的简单操作建议使用include配置,如果你需要炫酷的安装过程,建议使用script进行完全自定义。

NSIS对于处理安装包这种东西,功能非常的强大。但是学习起来并不比一门高级语言要容易。其中的奥秘还要各位大佬自行探索

这里上一些学习资源

  1. 关于操作系统的配置

主要是windows中64和32位的配置

CLI参数

electron-builder --ia32 // 32位
electron-builder        // 64位(默认)

nsis中配置

"win": {
  "icon": "build/icons/aims.ico",
  "target": [
    {
      "target": "nsis",
      "arch": [ // 这个意思是打出来32 bit + 64 bit的包,但是要注意:这样打包出来的安装包体积比较大,所以建议直接打32的安装包。
        "x64", 
        "ia32"
      ]
    }
  ]
}
  1. 更新配置

下面这个是给更新用的配置,主要是为了生成lastest.yaml配置文件

"publish": [
  {
    "provider": "generic", // 服务器提供商 也可以是GitHub等等
    "url": "http://xxxxx/" // 服务器地址
  }
],

完整配置

基本上可用的完整的配置

"build": {
    "productName":"xxxx",//项目名 这也是生成的exe文件的前缀名
    "appId": "com.leon.xxxxx",//包名  
    "copyright":"xxxx",//版权  信息
    "directories": { // 输出文件夹
      "output": "build"
    }, 
    "nsis": {
      "oneClick": false, // 是否一键安装
      "allowElevation": true, // 允许请求提升。 如果为false,则用户必须使用提升的权限重新启动安装程序。
      "allowToChangeInstallationDirectory": true, // 允许修改安装目录
      "installerIcon": "./build/icons/aaa.ico",// 安装图标
      "uninstallerIcon": "./build/icons/bbb.ico",//卸载图标
      "installerHeaderIcon": "./build/icons/aaa.ico", // 安装时头部图标
      "createDesktopShortcut": true, // 创建桌面图标
      "createStartMenuShortcut": true,// 创建开始菜单图标
      "shortcutName": "xxxx", // 图标名称
      "include": "build/script/installer.nsh", // 包含的自定义nsis脚本
    },
    "publish": [
      {
        "provider": "generic", // 服务器提供商 也可以是GitHub等等
        "url": "http://xxxxx/" // 服务器地址
      }
    ],
    "files": [
      "dist/electron/**/*"
    ],
    "dmg": {
      "contents": [
        {
          "x": 410,
          "y": 150,
          "type": "link",
          "path": "/Applications"
        },
        {
          "x": 130,
          "y": 150,
          "type": "file"
        }
      ]
    },
    "mac": {
      "icon": "build/icons/icon.icns"
    },
    "win": {
      "icon": "build/icons/aims.ico",
      "target": [
        {
          "target": "nsis",
          "arch": [
            "ia32"
          ]
        }
      ]
    },
    "linux": {
      "icon": "build/icons"
    }
  }

命令行参数(CLI)

Commands(命令):

  electron-builder build                    构建命名                      [default]
  electron-builder install-app-deps         下载app依赖
  electron-builder node-gyp-rebuild         重建自己的本机代码
  electron-builder create-self-signed-cert  为Windows应用程序创建自签名代码签名证书
  electron-builder start                    使用electronic-webpack在开发模式下运行应用程序(须臾要electron-webpack模块支持)

Building(构建参数):

  --mac, -m, -o, --macos   Build for macOS,                              [array]
  --linux, -l              Build for Linux                               [array]
  --win, -w, --windows     Build for Windows                             [array]
  --x64                    Build for x64 (64位安装包)                     [boolean]
  --ia32                   Build for ia32(32位安装包)                     [boolean]
  --armv7l                 Build for armv7l                              [boolean]
  --arm64                  Build for arm64                               [boolean]
  --dir                    Build unpacked dir. Useful to test.           [boolean]
  --prepackaged, --pd      预打包应用程序的路径(以可分发的格式打包)
  --projectDir, --project  项目目录的路径。 默认为当前工作目录。
  --config, -c             配置文件路径。 默认为`electron-builder.yml`(或`js`,或`js5`)

Publishing(发布):

  --publish, -p  发布到GitHub Releases [choices: "onTag", "onTagOrDraft", "always", "never", undefined]

Deprecated(废弃):

  --draft       请改为在GitHub发布选项中设置releaseType                 [boolean]
  --prerelease  请改为在GitHub发布选项中设置releaseType                 [boolean]
  --platform    目标平台 (请更改为选项 --mac, --win or --linux)
           [choices: "mac", "win", "linux", "darwin", "win32", "all", undefined]
  --arch        目标arch (请更改为选项 --x64 or --ia32)
                   [choices: "ia32", "x64", "armv7l", "arm64", "all", undefined]

Other(其他):

  --help     Show help                                                 [boolean]
  --version  Show version number                                       [boolean]

Examples(例子):

  electron-builder -mwl                        为macOS,Windows和Linux构建(同时构建)
  electron-builder --linux deb tar.xz          为Linux构建deb和tar.xz
  electron-builder -c.extraMetadata.foo=bar    将package.js属性`foo`设置为`bar`
  electron-builder --config.nsis.unicode=false 为NSIS配置unicode选项
    

TargetConfiguration(构建目标配置):

target:  String - 目标名称,例如snap.
arch “x64” | “ia32” | “armv7l” | “arm64”> | “x64” | “ia32” | “armv7l” | “arm64”  -arch支持列表

常见的错误

  • NPM下载的问题

    因为NPM在国内比较慢。导致electron-V.xxxx.zip下载失败。这些东西如果是第一次打包的话是需要下载对应electron版本的支持文件。解决办法有两个

    1. 设置镜像:在C盘User中找到.npmrc文件。然后加入下面这句代码,但是这个有时候也不是很好用
    ELECTRON_MIRROR=http://npm.taobao.org/mirrors/electron/
    
    1. 直接去淘宝镜像文件库找到对应的文件并下载,放到指定的目录下,electron的淘宝镜像地址。下载完之后放到指定的文件。一般文件得地址在C:\Users\Administrator\AppData\Local\electron\Cache。例如我要下载1.8.4版本的electron,那么找到镜像下得文件然后放到指定文件夹中。
      electron
      electron

    (如果是在执行npm install时下载不下来)直接在淘宝镜像下载对应版本的zip,然后扔到C:\Users\YourUserName.electron就行

    这就解决了这个问题,简单又暴力。

  • NSIS下载问题

    如果你要打NSIS得包还需要西再下载nsis-resources-xxx等等包。经过上面得经验这下我们知道缺什么就填什么呗,通过错误日志我们可以得到我们要下载得版本,一般错误中通常会展示github下载地址,直接点开连接去下载。但是位置这次不一样了。因为这是electron-builder的支持环境所以我们要放在C:\Users\Administrator\AppData\Local\electron-builder\cache\nsis\下了。

总结

electron-builder是一个简单又强大的库。解决了打包这个棘手的问题,而且可以应对大部分的打包需求。

patch-edid解决外接Mac外接显示器模糊问题

10.11 El Capitan中,需要拷貝override文件的地址變更為 /System/Library/Displays/Contents/Resources/Overrides
但是由於蘋果提升了系統安全性,用戶無法將文件拷貝入此文件夾(會出現禁止符號,沒有密碼提示)。

这是我之前的问题:
我的Mac由于工作需要,接入了一台超宽屏幕的外置显示器,分辨率是1560*1080.用的是Mini Display Port转Display Port接口,结果有一个问题。
什么都还好,就是字体十分发虚!网页上的字体还勉强,打开iwork编辑,字体只要小于12号根本没法看。
结果进入系统信息,发现这个显示器被mac识别为电视了!请看下图。

最后一项TV是yes。
请问如何让mac把它识别成显示器不是电视从而改善字体发虚的问题啊!网上的帖子都试过了无效,谢谢各路大神了!

解决方法:

这是因为Mac在识别显示器上不够智能,经常把显示器识别为电视,所以导致采用电视的渲染方式,使得字体模糊发虚。

所以我们要对显示器强制RGB渲染。

0.下載這個到你的Downloads文件夾 patch-edid.rb (1.49 KB, 下载次数: 1898)

1,打开终端,输入
cd Downloads
ruby patch-edid.rb

(这是一个命令,不要拆成两行来输入)

2,打开“下载”目录,你会发现多了一个文件夹(以我的为例:DisplayVendorID-5e3)

3,进入Finder以下目录:
/System/Library/Displays/Overrides

4,把那个文件夹拷贝进去(需要输入密码确认,如果有同名文件夹建议覆盖前先备份)

5,重新链接显示器。

你会惊奇的发现,显示效果好了100倍!

感謝11頁的swfes朋友提供適用於OSX 10.11 EL Capitan中的方法

10.11找到解决办法了!

  1. 暂时停用系统保护:
    按下开机键时即刻按住 command R(“R”字母键),中间的苹果标志及进度条出现后放开按键,等待恢复安装界面和 “OS X 实用工具” 窗口出现后,点击顶部菜单栏的 “实用工具”,在其下拉菜单点选运行 “终端”,在终端闪动字符的位置直接输入“csrutil disable” 并回车,重新启动电脑。
    2.把那个文件夹拷贝到/System/Library/Displays/Contents/Resources/Overrides

webpack简介与使用

CommonJS 和 AMD 是用于 JavaScript 模块管理的两大规范,前者定义的是模块的同步加载,主要用于 NodeJS ;而后者则是异步加载,通过 RequireJS 等工具适用于前端。随着 npm 成为主流的 JavaScript 组件发布平台,越来越多的前端项目也依赖于 npm 上的项目,或者自身就会发布到 npm 平台。因此,让前端项目更方便的使用 npm 上的资源成为一大需求。

web 开发中常用到的静态资源主要有 JavaScript、CSS、图片、Jade 等文件,webpack 中将静态资源文件称之为模块。 webpack 是一个 module bundler (模块打包工具),其可以兼容多种 js 书写规范,且可以处理模块间的依赖关系,具有更强大的 js 模块化的功能。Webpack 对它们进行统一的管理以及打包发布,其官方主页用下面这张图来说明 Webpack 的作用.

webpack 介绍

webpack 更 Gulp 的作用相同,是项目构建工具。

webpack 和 Gulp 的区别

Gulp 出现的比较早,更适合于做任务型的,可以处理任何的网站静态网站、SPA、Node.js 项目代码,Gulp 里面就是一堆的任务;
Webpack 一般全部用来处理 SPA 应用,就 React、Vue.js、AngularJS 使用。

所以使用的场景不一样,因为内部的原理不同。

webpack 官网文档

官网地址:http://webpack.github.io/docs/

webpack 的优势

对 CommonJS 、 AMD 、ES6 的语法做了兼容
对 js、css、图片等资源文件都支持打包
串联式模块加载器以及插件机制,让其具有更好的灵活性和扩展性,例如提供对 CoffeeScript、ES6的支持
有独立的配置文件 webpack.config.js
可以将代码切割成不同的 chunk,实现按需加载,降低了初始化时间
支持 SourceUrls 和 SourceMaps,易于调试
具有强大的 Plugin 接口,大多是内部插件,使用起来比较灵活
webpack 使用异步 IO 并具有多级缓存。这使得 webpack 很快且在增量编译上更加快

webpack 的使用

新建项目

在项目根目录下运行:

    $ npm init -y

src 中的开发文件,dist 是打包后的文件

安装

    $ npm install webpack -g
    $ npm install webpack -save-dev
    $ npm install react -save

配置文件

webpack.develop.config.js

    // webpack 的开发配置文件
    // 编写配置文件,要有最基本的文件入口和输出文件配置信息等
    // 里面还可以加loader和各种插件配置使用
    var path = require('path');
    module.exports = {
        // 单页面 SPA 的入口文件
        entry:path.resolve(__dirname,'src/js/app.js'),
        // 构建之后的文件输出位置配置
        output: {
            path: path.resolve(__dirname, 'dist'),
            filename: 'bundle.js'
        }
    };

运行

  $ webpack --config webpack.develop.config.js

进行版本控制

  $ git init
  $ git status
  $ git add -A
  $ git commit -m "项目目录结构及 webpack 初步配置"

webpack 启动过程演进

把运行命令配置到 npm 的 script 中。 package.json

  "scripts": {
      "test": "echo \"Error: no test specified\" && exit 1",
      "develop": "webpack --config webpack.develop.config.js",
      "publish": "webpack --config webpack.publish.config.js"
  }

执行 :

  $ npm run develop

更好的方式实现动启动

如果需要一直输入 npm run develop 确实是一件非常无聊的事情,我们可以把让他安静的运行,让我们设置 webpack-dev-server

除了提供模块打包功能,Webpack 还提供了一个基于 Node.js Express 框架的开发服务器,它是一个静态资源 Web 服务器,对于简单静态页面或者仅依赖于独立服务的前端页面,都可以直接使用这个开发服务器进行开 发。在开发过程中,开发服务器会监听每一个文件的变化,进行实时打包,并且可以推送通知前端页面代码发生了变化,从而可以实现页面的自动刷新。

更好的方式实现自动启动:webpack 官方提供的一个第三个的插件,自动监听代码变化,帮我们重新构建,把 webpack 和 express 封装了

  $ npm install webpack-dev-server -save-dev

调整 npm 的 package.json scripts 部分中开发命令的配置

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "develop": "webpack-dev-server --config webpack.develop.config.js --devtool eval --progress --colors --hot --content-base src",
    "publish": "webpack --config webpack.publish.config.js"
  }

webpack-dev-server - 在 localhost:8080 建立一个 Web 服务器
--devtool eval - 为你的代码创建源地址。当有任何报错的时候可以让你更加精确地定位到文件和行号
--progress - 显示合并代码进度
--colors -- hot,命令行中显示颜色!
--content-base 指向设置的输出目录//这点一定是我们的发布目录

在 src 下面,新建一个 index.html 文件,

  <!DOCTYPE html>
  <html lang="en">
  <head>
      <meta charset="UTF-8">
      <title>webpack 使用</title>
  </head>
  <body>
      <div id="app"></div>
  </body>
  <script src="bundle.js"></script>
  </html>

执行 npm run develop 之后我们发现执行没有结束,启动着监听,并在 8080 端口开启了一个服务器。

如果修改了 app.js 文件,会自动执行构建,刷新浏览器会发生变化。

在 index.html 访问的时候,会访问 bundle.js 文件,为什么,因为 webpack-dev-server 生成的 bundle 在内存中,放到内存中构建快

总的来说,当你运行 npm run develop 的时候,会启动一个 Web 服务器,然后监听文件修改,然后自动重新合并你的代码。真的非常简洁!

注意:

用 webpack-dev-server 生成 bundle.js 文件是在内存中的,并没有实际生成
如果引用的文件夹中已经有 bundle.js 就不会自动刷新了,你需要先把 bundle.js 文件手动删除
用 webstorm 需要注意,因为他是自动保存的,所以可能识别的比较慢,你需要手动的 ctrl+s 一下

浏览器自动刷新

修改 webpack.develop.config.js 的入口文件配置,修改 entry 部分如下:

    var path = require('path');
    module.exports = {
        // 单页面 SPA 的入口文件
        entry:[
            // 实现浏览器自动刷新
            'webpack/hot/dev-server',
            'webpack-dev-server/client?http://localhost:8080',
            path.resolve(__dirname,'src/js/app.js')
        ],
        // 构建之后的文件输出位置配置
        output: {
            path: path.resolve(__dirname, 'dist'),
            filename: 'bundle.js'
        }
    };

修改了配置文件,重新启动,执行 npm run develop .


常用加载器

Loader:这是webpack准备的一些预处理工具

在构建项目之前做一些预处理操作,比如 ES6 转 ES5,Sass、Less

编译 JSX 和 ES6 到 ES5 语法的加载器

安装:

  $ npm install babel-loader --save-dev
  $ npm install babel-core babel-preset-es2015 babel-preset-react --save-dev

babel-loader: 转换器,编译 JSX 语法和 ES6 语法到 ES5 语法。

修改开发配置环境: webpack.develop.config.js

    module: {
        loaders: [
            {
                test: /\.jsx?$/, // 用正则来匹配文件路径,这段意思是匹配 js 或者 jsx
                loader: 'babel', // 加载模块 "babel" 是 "babel-loader" 的缩写
                query: {
                    presets: ['es2015', 'react']
                }
            }
        ]
    }

一个 React Hello, World! app.js 文件

    // 项目入口文件
    import React, {Component} from 'react';
    import ReactDOM from 'react-dom';
    ReactDOM.render(
        <div>
            Hello World!
        </div>,
        document.getElementById('app')
    );

加载 CSS

webpack 允许像加载任何代码一样加载 CSS。可以选择需要的方式,但是可以为每个组件把所有的 CSS 加载到入口主文件中来做任何事情。

加载 CSS 需要 css-loader 和 style-loader,他们做两件不同的事情:

css-loader 会遍历 CSS 文件,然后找到 url() 表达式然后处理他们
style-loader 会把原来的 CSS 代码插入页面中的一个 style 标签中

安装

  $ npm install css-loader style-loader --save-dev

新建文件夹:components

新增:_base.css Hello.css Hello.js Hello.sass 文件

修改配置文件:

  // 可以在 js 中引用 css 的加载器
  {
      test: /\.css$/,
      loader: 'style!css' // 如果同时使用多个加载器,中间用 ! 连接,加载器的执行顺序是从右向左
  }

!用来定义loader的串联关系,"-loader"是可以省略不写的,多个loader之间用“!”连接起来

Css加载策略

1、在主入口文件中,比如 src/app.js 你可以为整个项目加载所有的 CSS

  import  './project-styles.css';

CSS 就完全包含在合并的应用中,再也不需要重新下载。

2、懒加载(推荐)

如果想发挥应用中多重入口文件的优势,可以在每个入口点包含各自的 CSS。

把模块用文件夹分离,每个文件夹有各自的 CSS 和 JavaScript 文件。
再次,当应用发布的时候,导入的 CSS 已经加载到每个入口文件中。

3、定制组件css

可以根据这个策略为每个组件创建 CSS 文件,可以让组件名和 CSS 中的 class 使用一个命名空间,来避免一个组件中的一些 class 干扰到另外一些组件的 class。如下图:

4、使用内联样式取代 CSS 文件

在 “React Native” 中不再需要使用任何 CSS 文件,只需要使用 style 属性,可以把你的 CSS 定义成一个对象,那样就可以根据项目重新来考略你的 CSS 策略。

加载sass

下载依赖

  $ npm install sass-loader -save-dev

修改配置文件

  // 可以在 js 中引用 sass 的加载器
  {
      test: /\.scss$/,
      loader: 'style!css!sass'
  }

安装sass-loader之后运行运行 npm run develop 时报错

解决:

  $ npm install node-sass -save-dev

图片处理

直到 HTTP/2 才能在应用加载的时候避免设置太多 HTTP 请求。
根据浏览器不同必须设置并行请求数,如果在 CSS 中加载了太多图片的话,可以自动把这些图片转成 BASE64 字符串然后内联到 CSS 里来降低必要的请求数,这个方法取决于图片大小。
需要为应用平衡下载的大小和下载的数量,不过 Webpack 可以让这个平衡十分轻松适应。

下载载依赖

  $ npm install url-loader  file-loader --save-dev

修改配置文件:

  {
      test: /\.(png|jpg|gif|jpeg)$/,
      loader: 'url?limit=25000'
  },
  // 处理字体
  {
      test: /\.(eot|woff|ttf|woff2|svg)$/,
      loader: 'url?limit=25000'
  }

加载器会把需要转换的路径变成 BASE64 字符串,在其他的 webpack 书中提到的这方面会把 CSS 中的 “url()” 像其他 require 或者 import 来处理。
意味着如果我们可以通过它来处理我们的图片文件。
url-loader 传入的 limit 参数是告诉它图片如果不大于 25KB 的话要自动在它从属的 css 文件中转成 BASE64 字符串。

大图片处理

在代码中是一下情况:

  div.img {
      background: url(../image/xxx.jpg)
  }
  //或者
  var img = document.createElement("img");
  img.src = require("../image/xxx.jpg");
  document.body.appendChild(img);
  // 可以这样配置
  module: {
      {
        test: /\.(png|jpg)$/,
        loader: 'url-loader?limit=10000&name=build/[name].[ext]'
      }]
  }

针对上面的两种使用方式,loader 可以自动识别并处理。根据 loader 中的设置,webpack 会将小于指点大小的文件转化成 base64 格式的 dataUrl,其他图片会做适当的压缩并存放在指定目录中。


webpack 的部署策略

修改 npm 的 package.json 文件

"publish": " webpack --config webpack.publish.config.js -p",

指向生产的配置文件,并且加上了webpack的cli的-p,他会自动做一些优化

分离应用和第三方

何时应该分离?

当应用依赖其他库尤其是像 React JS 这种大型库的时候,需要考虑把这些依赖分离出去,这样就能够让用户在更新应用之后不需要再次下载第三方文件。

当满足下面几个情况的时候你就需要这么做了:

1、当你的第三方的体积达到整个应用的 20% 或者更高的时候。
2、更新应用的时候只会更新很小的一部分
3、你没有那么关注初始加载时间,不过关注优化那些回访用户在你更新应用之后的体验。
4、有手机用户。

修改 webpack.publish.config.js 文件

  var path = require('path');
  var node_modules = path.resolve(__dirname, 'node_modules');
  module.exports = {
      entry: path.resolve(__dirname,'src/js/app.js'),
      output: {
          path: path.resolve(__dirname, 'build'),
          filename: 'bundle.js',
      },
      // ...
      plugins: [
          new CleanPlugin(['dist']),
          // 分离第三方应用插件,name属性会自动指向entry中vendros属性,filename属性中的文件会自动构建到output中的path属性下面
          new webpack.optimize.CommonsChunkPlugin({name: 'vendors', filename: 'vendors.js'}),
          // 可以新建多个抽离样式的文件,这样就可以有多个css文件了。
          new ExtractTextPlugin("app.css"),
          new webpack.DefinePlugin({
              //去掉react中的警告,react会自己判断
              'process.env': {
                  NODE_ENV: '"production"'
              }
          })
      ]
  }

可以看到,其实生产环境的配置和开发的配置没有太大的不同,主要是把不需要的东西给去掉了

  $ npm  run  publish
  <!DOCTYPE html>
  <html lang="en">
  <head>
      <meta charset="UTF-8">
      <title>webpack 使用</title>
  </head>
  <body>
      <div id="app"></div>
  </body>
  <script src="bundle.js"></script>
  <script src="vendors.js"></script>
  </html>

注意:记住要把这些文件都加入到你的 HTML 代码中,但在上面这种引入后,在浏览器打开之后报错,是因为引入顺序的问题

将上面 index.html 文件中的两个 js 文件引入顺序调换,如下

  <!DOCTYPE html>
  <!-- ... -->
  <script src="vendors.js"></script>
  <script src="bundle.js"></script>
  </html>

和 gulp 的集成

  // gulp 的任务是控制执行流程,webpack 的任务是处理复杂引用的依赖
  var gulp = require('gulp');
  // 删除文件和目录
  var del = require('del');
  // 按顺序执行
  var gulpSequence = require('gulp-sequence');
  // 引入webpack的本地模块
  var webpack = require("webpack");
  // 引入wbpack的配置文件
  var webpackConfig = require("./webpack.publish.config.js");
  gulp.task('default', ['sequence'], function() {
      console.log("项目构建成功");
  });
  // 流程控制
  gulp.task('sequence', gulpSequence('clean','webpack'));
  // 删除文件和文件夹
  gulp.task('clean', function(cb) {
      //del('dist);// 如果直接给dist的目录,项目启动的顺序还有清除结果会报错,所以要写的更详细一些
      del(['dist/js','dist/css','dist/img','dist/*.html']);
      setTimeout(function(){
          return cb();
      },3000)

  });
  //写一个任务,在gulp中执行webpack的构建
  // gulp 负责任务流程部分的操作,webpack 负责复杂模块系统的引用分离工作
  gulp.task('webpack', function(cb) {
      setTimeout(function(){
          // 执行webpack的构建任务
          webpack(webpackConfig, function (err, stats) {
              if (err){
                  console.log("构建任务失败");
              }else{
                  cb();
              }
          });
      },3000)
  });

合并成单文件

一般情况下只有在下面的情况下才使用单入口模式:

1、应用很小
2、很少会更新应用
3、你不太关心初始加载时间

gulp + webpack 构建多页面前端项目

http://cnodejs.org/topic/56df76559386fbf86ddd6916


常用插件

压缩插件 webpack.optimize.UglifyJsPlugin, 这个插件是webpack自带的.

在配置文件中加入以下代码:

  plugins: [
    new webpack.optimize.UglifyJsPlugin({
          compress: {
              warnings: false
          }
      })
  ]

提取css插件

在 webpack 中编写 js 文件时,可以通过 require 的方式引入其他的静态资源,可通过 loade r对文件自动解析并打包文件。通常会将 js 文件打包合并,css文件会在页面的header中嵌入style的方式载入页面。但开发过程中我们并不想将样式打在脚本中,最好可以独立生成css文 件,以外链的形式加载。这时extract-text-webpack-plugin插件可以帮我们达到想要的效果。需要使用npm的方式加载插件,然后 参见下面的配置,就可以将js中的css文件提取,并以指定的文件名来进行加载。

  $ npm install extract-text-webpack-plugin --save-dev

只能把 css 抽出来,但是 sass 的样式不能分离出来。

  var ExtractTextPlugin = require("extract-text-webpack-plugin");
  plugins: [
    new ExtractTextPlugin("app.css")
  ]

自动创建 index.Html 页面插件

html-webpack-plugin

  var HtmlWebpackPlugin = require('html-webpack-plugin');
  plugins: [
    new HtmlWebpackPlugin({
        template: './src/template.html',
        htmlWebpackPlugin: {
            "files": {
                "css": ["app.css"],
                "js": ["vendors.js", "bundle.js"]
            }
        },
        // 压缩 html 文档
        minify: {
            removeComments: true,
            collapseWhitespace: true,
            removeAttributeQuotes: true
        }
    })
  ]

优化第三方包

  plugins: [
    new webpack.DefinePlugin({
        //去掉react中的警告,react会自己判断
        'process.env': {
            NODE_ENV: '"production"'
        }
    })
  ]

自动打开浏览器插件

open-browser-webpack-plugin

https://github.com/baldore/open-browser-webpack-

webpack.develop.config.js

  // 自动打开浏览器插件
  var OpenBrowserPlugin = require('open-browser-webpack-plugin');
  plugins: [
      new OpenBrowserPlugin({url: 'http://localhost:8080/', browser: 'chrome'})
  ]

提取 js 公共部分插件

提取公共文件: CommonsChunkPlugin

  plugins: [
      // 分离第三方应用插件,name属性会自动指向 entry 中 vendros 属性,filename 属性中的文件会自动构建到 output 中的 path 属性下面
      new webpack.optimize.CommonsChunkPlugin({name: 'vendors', filename: 'vendors.js'}),
  ]

ProvidePlugin插件

自动添加引用插件,全局暴露插件,直接使用

删除目录插件

clean-webpack-plugin

  var CleanPlugin = require("clean-webpack-plugin");
  plugins: [
    new CleanPlugin(['dist']),
  ]

拷贝文件插件

copy-webpack-plugin

合并配置文件插件

webpack-config

https://github.com/mdreizin/webpack-config


开发阶段代码风格控制 eslint

安装:

  $ npm install eslint -g
  $ npm install eslint-loader -save-dev
  module : {
    preLoaders: [
        {test: /\.js$/, loader: "eslint-loader", exclude: /node_modules/}
    ],
  }

其它知识点

webpack中的非入口文件(异步加载)

这个是重点要配合 chunkname 属性,react-router 的动态路由会用到

http://react-china.org/t/webpack-output-filename-output-chunkfilename/2256/2

基本上都是在 require.ensure 去加载模块的时候才会出现,chunkFileName,个人理解是 cmd 和 amd 异步加载而且没有给入口文件时,会生成了 no-name 的 chunk,所以 chunkFileName一般都会是 [id].[chunkhash].js, 也就是这种 chunk 的命名一般都会是 0.a5898fnub6.js.

Resolve属性

webpack 在构建包的时候会按目录的进行文件的查找,resolve 属性中的 extensions 数组中用于配置程序可以自行补全哪些文件后缀:

  resolve: {
      //查找module的话从这里开始查找
      root: '/pomy/github/flux-example/src', //绝对路径
      //自动扩展文件后缀名,意味着我们require模块可以省略不写后缀名
      //注意一下, extensions 第一个是空字符串! 对应不需要后缀的情况.
      extensions: ['', '.js', '.json', '.scss',’jsx’],

      //模块别名定义,方便后续直接引用别名,无须多写长长的地址
      alias: {
          AppStore : 'js/stores/AppStores.js',//后续直接 require('AppStore') 即可
          ActionType : 'js/actions/ActionType.js',
          AppAction : 'js/actions/AppAction.js'
      }
  }

Externals属性

外部依赖不需要打包进 bundle,当我们想在项目中 require 一些其他的类库或者 API ,而又不想让这些类库的源码被构建到运行时文件中,这在实际开发中很有必要。
比如:在页面里通过 script 标签引用了 jQuery:<script src="//code.jquery.com/jquery-1.12.0.min.js"></script>,所以并不想在其他 js 里再打包进入一遍,比如你的其他 js 代码类似:

其实就是不是通过require或者import引入的,而是直接写在html中的js地址。

    // 配置了这个属性之后 react 和 react-dom 这些第三方的包都不会被构建进 js 中,那么我们就需要通过 cdn 进行文件的引用了
    // 前边的这个名称是在项目中引用用的,相当于 import React from 'react1' 中的 react
    externals: {
        'react1': 'react',
        'react-dom1': 'react-dom',
        '$1': 'jQuery'
    },

这样用了 externals 属性时不用分离插件了,作用是这里引的插件不会被 webpack 所打包。要么用 cdn 要么需要 webpack 打包。

开发环境中使用压缩文件

http://fakefish.github.io/react-webpack-cookbook/Optimizing-rebundling.html

不使用就会把 react 再处理一遍

noParse属性

module.noParse 是 webpack 的另一个很有用的配置项,如果确定一个模块中没有其他新的依赖项就可以配置这个像,webpack 将不再扫描这个文件中的依赖。

  module: {
    noParse: [/moment-with-locales/]
  }

多文件入口

http://fakefish.github.io/react-webpack-cookbook/Multiple-entry-points.html

强制从新加载文件

http://fakefish.github.io/react-webpack-cookbook/Optimizing-caching.html

Chunk

代码分离:

http://webpack.github.io/docs/code-splitting.html

懒加载

(1)、在 react 中如何使用

http://fakefish.github.io/react-webpack-cookbook/Lazy-loaded-entry-points.html

(2)、在 react-router 中用到动态加载路由可以实现

在服务器端用 webpack

Node 和webpack 集成用到的中间件:http://www.tuicool.com/articles/IvQb2ey
Node 和webpack 集成过程中遇到的坑如何解决:http://www.tuicool.com/articles/zEZneuq

不推荐用 webpack 构建 Node 代码

热加载组件

http://fakefish.github.io/react-webpack-cookbook/Hot-loading-components.html

Swift爬坑路漫漫

Git 修改commit历史的常用方法

开发时经常会出现以下场景

请把你当前分支与master 最新版本做一次rebase,我们在合并你的PR
你把你几个commits squash成一个commit,避免git历史过于复杂和难看
你去把你commit 的message修改一下,使得commit 的信息让人更清晰易懂

上述的这些问题常见于PR下面的讨论,下面总结一下解决问题的方法

重写最近的commit 的信息

在提交后, 如果你发现在其描述中发现typo,或者找到一个更好的方式来描述commit。要进行校正:

git commit --amend

这时git将进入编辑模式,在这里你可以修改之前的commit 的相关信息

除了简单的编辑commit记录之外,你也可以单单修改coomit的author

git commit --amend --author="waltcow <[email protected]>"

修改其它的commit的内容

使用 Interactive Rebase 模式

git rebase 重新把每次的提交一个一个按顺序apply在当前的branch上。git rebase 接受几个使用的option,其中有个非常实用的就是 --interactive- i 是对应的shortcut),进入Interactive Rebase 模式后,git将打开编辑器,list出包含的改动信息,这些list的每行开始都接受command ,用户可以决定相关操作的参数后再进行rebase

详细可以见下面的相关例子

现在我要重写当前分支上最后4个commit的信息,我只需要

git rebase -i HEAD~4

下面我们将会看到

pick 07c5abd Introduce OpenPGP and teach basic usage
pick de9b1eb Fix PostChecker::Post#urls
pick 3e7ee36 Hey kids, stop all the highlighting
pick fa20af3 git interactive rebase, squash, amend

# Rebase 8db7e8b..fa20af3 onto 8db7e8b
#
# Commands:
#  p, pick = use commit
#  r, reword = use commit, but edit the commit message
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#  f, fixup = like "squash", but discard this commit's log message
#  x, exec = run command (the rest of the line) using shell
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

上面我们可以看到最近的commit记录,时间排序从older=>newer,
下面有相关的command的使用介绍,如下将逐一介绍

  • pick 这是默认的选择,就是把commit再重新apply一次而已,commit的内容和信息都不会有改动
  • reword commit的内容不变,仅仅把commit的message做修改

比如我把上面的commit改成

pick 07c5abd Introduce OpenPGP and teach basic usage
pick de9b1eb Fix PostChecker::Post#urls
reword 3e7ee36 Hey kids, stop all the highlighting
pick fa20af3 git interactive rebase, squash, amend
+
When I save and quit the editor, git will follow the described commands, landing

当我:wq 后git 将会输出

robots.thoughtbot.com tc-git-rebase % git rebase -i HEAD~4
[detached HEAD dd62a66] Stop all the highlighting
 Author: Caleb Thompson
 Date: Fri Oct 31 10:52:26 2014 -0500
 2 files changed, 39 insertions(+), 42 deletions(-)
Successfully rebased and updated refs/heads/tc-git-rebase.

将多个commits Squash在一起

  • squash 将提交融合到前一个commit(上面的那一行)
  • fixup 类似于 squash,但是commit的message将会被丢弃

下面是使用案例
现在我们我4个commit,前三个commit是我同事改的,我自己的是最后一个

pick 07c5abd Introduce OpenPGP and teach basic usage
pick de9b1eb Fix PostChecker::Post#urls
pick 3e7ee36 Hey kids, stop all the highlighting
pick fa20af3 git interactive rebase, squash, amend

假如我现在想把我同事的修改合并在一起,以为前三个都是做一个的主题的,这样合并后的话,我们以后如果想revert这部分变更将变简单。
前三个commit中我想只保留第一个commit的message,后面两个的更改提交并压缩到第一个提交消息中

pick 07c5abd Introduce OpenPGP and teach basic usage
squash  de9b1eb Fix PostChecker::Post#urls
squash  3e7ee36 Hey kids, stop all the highlighting
pick fa20af3 git interactive rebase, squash, amend

squash 的作用就是把当前commit向前合并,一直合并到pick为止
fixupsquash非常类似,把pick修改成fixup,同样会向前合并到pick,唯一的区别就是,fixup会忽略当前commit的信息,只会应用修改。

与远程master分支做 Rebase

当我们fork一个远程仓库,在一个功能分支上开始工作,并且upstream/maste将不断的前进。我们的历史看起来像:

      A---B---C feature
    /
D---E---F---G upstream/master


当仓库管理者在合并你的PR时都会教你 rebase on top of master,这样做可以避免merge时出现冲突,并保证了两个分支是基于一个parent 的,在rebase后你所看到的历史是这样的

A'--B'--C' feature
             /
D---E---F---G upstream/master

一般使用的命令

# Point our `upstream` remote to the original fork
git remote add upstream https://github.com/thoughtbot/factory_girl.git

# Fetch latest commits from `upstream` (the original fork)
git fetch upstream

# Checkout our feature branch
git checkout feature

# Reapply it onto upstream's master
git rebase upstream/master

# Fix conflicts, then `git rebase --continue`, repeat until done
# Push to our fork
git push --force origin feature

Babel相关配置备忘

Babel介绍

Babel 把用最新标准编写的 JavaScript 代码(ES6, ES7)向下编译成可以在今天随处可用的版本(ES3,ES5)。 这一过程叫做“源码到源码”编译, 也被称为转换编译。并且还可以支持React的JSX写法。

15 年 11 月,Babel 发布了 6.0 版本。相较于前一代 Babel 5,新一代 Babel 更加模块化, 将所有的转码功能以插件的形式分离出去,默认只提供 babel-core。原本只需要装一个 babel ,现在必须按照自己的需求配置,无需下载大量无用的依赖

在命令行中使用 Babel

因为 Babel 的不同版本以及不同转码规则会起到不同的效果,全局安装会带来不必要的麻烦。在命令提示符中转到自己的项目目录下:

$ npm install --global babel-cli
# or
$ npm install --save-dev babel-core
  • 如果你只想在命令行中使用babel 只需安装 babel-cli
  • 如果是要在Node上以编程的方式来使用 Babel ,则需要安装 babel-core

babel-core 的作用是把 js 代码分析成 ast ,方便各个插件分析语法进行相应的处理。有些新语法在低版本 js 中是不存在的,如箭头函数,rest 参数,函数默认值等,这种语言层面的不兼容只能通过将代码转为 ast,分析其语法后再转为低版本 js。首先安装 babel-core

如果你之前执行了全局安装,可以通过下面的代码卸载全局范围的 Babel。放心,这并不会影响到项目路径下的本地安装。

npm uninstall --global babel-cli

添加相关的plugin和preset

因为在babel6中没有默认的转换规则,当你没加任何配置去转换一个文件时,babel 只会把文件直接原码输出,不做任何的改变

如果你想转义一些相关的新特性,例如 arrow function
你需要先安装arrow function的相关plugin

npm install --save-dev babel-plugin-transform-es2015-arrow-functions

然后修改本地的.babelrc文件,加入以下的配置

{
  "plugins": ["transform-es2015-arrow-functions"]
}
// before
var foo = () => { 
  console.log('foo')
}

//after

'use strict';

var foo = function foo() {
  console.log('foo');
};

babel的plugin是往下兼容的,比如在ES2015中常量是 let,如果你想在ES5中运作则需额外的添加plugin编译

$ npm install --save-dev babel-plugin-check-es2015-constants
$ npm install --save-dev babel-plugin-transform-es2015-block-scoping
{
  "plugins": [
    "check-es2015-constants",
    "transform-es2015-block-scoping"
  ]
}

由于这些的依赖很难去记住,如果你不想刻意去定制,你只要配一下preset(就是一堆插件的集合)
preset类似于餐厅的套餐。如 babel-preset-es2015 打包了 es2015 的特性,babel-preset-stage-0 打包处于Strawman 初稿 阶段的语法,babel-preset-react包含了React的转码规则

babel-preset-stage-x的意义

以下是babel在4 个不同阶段的预设:

  • babel-preset-stage-0
  • babel-preset-stage-1
  • babel-preset-stage-2
  • babel-preset-stage-3

注意 stage-4 预设是不存在的因为它就是上面的 es2015 预设。

JavaScript 还有一些提案,正在积极通过 TC39(ECMAScript 标准背后的技术委员会)的流程成为标准的一部分。
这个流程分为 5(0-4)个阶段。 随着提案得到越多的关注就越有可能被标准采纳,于是他们就继续通过各个阶段,最终在阶段 4 被标准正式采纳。

  • 阶段 0: Strawman 初稿

一个推进 ECMAScript 发展的自由形式的想法。该想法必须由 TC39 的会员提交,如果是非会员则必须注册成为 TC39 贡献者才能提交。

  • 阶段 1:Proposal 建议

必须确定一位带头人来为负责这份建议。无论是带头人或者联合带头人都必须是 TC39 的会员(原文)。建议要解决的问题必须以简明的文字描述,而解决方案则要给出相应的实例和 API,并详细描述语义及算法。最后,必须指明此建议的潜在问题,例如与其他特性之间的关联,实现难点等。

  • 阶段 2:Draft 草案

草案是规范的第一个版本。其与最终标准中包含的特性不会有太大差别。建议此时必须要附加该特性的语法和语义的正式说明(使用 ECMAScript 标准的形式语言)。说明应该尽可能完善,但可以包含待办事项和占位符。该特性需要两个实验性的实现,其中一个可以在类似 Babel 的转译器(transpiler)中实现。

  • 阶段 3:Candidate 候选

候选阶段,建议基本完成,此时将从实现过程和用户使用两方面获取反馈来进一步完善建议。必备条件:规范文档必须是完整的。指定的评审人(由 TC39 而不是带头人指定)和 ECMAScript 规范的编辑须在规范上签字。还有至少要两个符合规范的实现(不必指定默认实现)。

  • 阶段 4:Finished 完成

建议已经准备就绪,可以添加到标准之中。

运行Babel 生成的代码

Babel 默认只转码 ES6 的新语法(syntax),而不转换新的API,比如 Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise 等全局对象,以及一些定义在全局对象上的方法(比如 Object.assign、Array.from)都不会转码。如果想让这写方法运行, babel 社区提供两种方案可以选择

  • babel-polyfill
  • babel-runtime + babel-plugin-transform-runtime

两个方案功能几乎相同,就是转码新增 API ,模拟 ES6 环境,但实现方法完全不同

babel-polyfill 的做法是将全局对象通通污染一遍,比如想在 node 0.10 上用 Promise,调用 babel-polyfill 就会往 global 对象挂上Promise 对象。对于普通的业务代码没有关系,但如果用在模块上就有问题了,会把模块使用者的环境污染掉。

babel-runtime 的作用也是模拟 ES2015 环境。只不过,babel-polyfill 是针对全局环境的,引入它,我们的浏览器就好像具备了规范里定义的完整的特性 – 虽然原生并未实现。babel-runtime 更像是分散的 polyfill 模块,我们可以在自己的模块里单独引入,比如 require(‘babel-runtime/core-js/promise’) ,它们不会在全局环境添加未实现的方法,只是,这样手动引用每个 polyfill 会非常低效。我们借助 Runtime transform 插件来自动化处理这一切。

$ npm install --save-dev babel-plugin-transform-runtime
$ npm install --save babel-runtime

// babelrc文件
 {
    "plugins": [
     "transform-runtime",
      "transform-es2015-classes"
    ]
  }
现在,Babel 会把这样的代码:

class Foo {
  method() {}
}

编译成:

import _classCallCheck from "babel-runtime/helpers/classCallCheck";
import _createClass from "babel-runtime/helpers/createClass";

let Foo = function () {
  function Foo() {
    _classCallCheck(this, Foo);
  }

  _createClass(Foo, [{
    key: "method",
    value: function method() {}
  }]);

  return Foo;
}();

swift4 语法备忘 - Inheritance

Inheritance

Class can inherit methods, properties, and other characteristics from another class. When one class inherits from another, the inheriting class is known as a subclass, and the class it inherits from is known as its superclass.

Base Class

Any class that does not inherit from another class is known as a base class.

Swift classes do not inherit from a universal base class. Classes you define without specifying a superclass automatically become base classes for you to build upon.

class Vehicle {
    var currentSpeed = 0.0
    var description: String {
        return "traveling at \(currentSpeed) miles per hour"
    }
    func makeNoise() {
        // do nothing - an arbitrary vehicle doesn't necessarily make a noise
    }
}

let someVehicle = Vehicle()
print("Vehicle: \(someVehicle.description)")

Sub Class

Subclassing is the act of basing a new class on an existing class. The subclass inherits characteristics from the existing class, which you can then refine. You can also add new characteristics to the subclass.

class SomeSubclass: SomeSuperclass {
    // subclass definition goes here
}

class Bicycle: Vehicle {
    var hasBasket = false
}

let bicycle = Bicycle()
bicycle.hasBasket = true
bicycle.currentSpeed = 15.0

print("Bicycle: \(bicycle.description)")
// Bicycle: traveling at 15.0 miles per hour

Overriding

A subclass can provide its own custom implementation of an instance method, type method, instance property, type property, or subscript that it would otherwise inherit from a superclass. This is known as overriding.

Accessing Superclass Methods, Properties, and Subscripts

Where this is appropriate, you access the superclass version of a method, property, or subscript by using the super prefix:

  • An overridden method named someMethod() can call the superclass version of someMethod() by calling super.someMethod() within the overriding method implementation.

  • An overridden property called someProperty can access the superclass version of someProperty as super.someProperty within the overriding getter or setter implementation.

  • An overridden subscript for someIndex can access the superclass version of the same subscript as super[someIndex] from within the overriding subscript implementation.

// Overriding Methods
class Train: Vehicle {
    override func makeNoise() {
        print("Choo Choo")
    }
}

// Overriding Property Getters and Setters
class Car: Vehicle {
    var gear = 1
    override var description: String {
        return super.description + " in gear \(gear)"
    }
}

// Overriding Property Observers
class AutomaticCar: Car {
    override var currentSpeed: Double {
        didSet {
            gear = Int(currentSpeed / 10.0) + 1
        }
    }
}

Preventing Overrides

You can prevent a method, property, or subscript from being overridden by marking it as final.

  • Writing the final modifier before the method, property, or subscript’s introducer keyword (such as final var, final func, final class func, and final subscript).

  • You can mark an entire class as final by writing the final modifier before the class keyword in its class definition (final class). Any attempt to subclass a final class is reported as a compile-time error.

使用Typescript的用法备忘

  1. Record 可以保证映射完整:
export enum EnvironmentType {
    development = "development",
    staging = "staging",
    production = "production"
}

export interface EnvironmentConfig {
    server: string
    port: number
}

type EnvironmentConfigMap = Record<EnvironmentType, EnvironmentConfig>
  1. infer
type FlattenIfArray<T> = T extends (infer R)[] ? R : T

Check if our generic Type is the array, If it is array extract the real type from it,
If it does not leave it as is

swift4 语法备忘 - Properties

Properties

Properties associate values with a particular class, structure, or enumeration.

  • Computed properties are provided by classes, structures, and enumerations. Stored properties are provided only by classes and structures.

  • Stored and computed properties are usually associated with instances of a particular type.

  • Properties can also be associated with the type itself. Such properties are known as type properties.

  • Define property observers to monitor changes in a property’s value

Stored Properties

stored property is a constant or variable that is stored as part of an instance of a particular class or structure. Stored properties can be introduced by the var or let keyword

Default value can be provided for a stored property as part of its definition

struct FixedLengthRange {
    var firstValue: Int
    let length: Int
}
var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3)
// the range represents integer values 0, 1, and 2
rangeOfThreeItems.firstValue = 6
// the range now represents integer values 6, 7, and 8

Stored Properties of Constant Structure Instances

If you create an instance of a structure and assign that instance to a constant, you cannot modify the instance’s properties, even if they were declared as variable properties:

let rangeOfFourItems = FixedLengthRange(firstValue: 0, length: 4)
// this range represents integer values 0, 1, 2, and 3
rangeOfFourItems.firstValue = 6
// this will report an error, even though firstValue is a variable property

Lazy Stored Properties

A lazy stored property is a property whose initial value is not calculated until the first time it is used. You indicate a lazy stored property by writing the lazy modifier before its declaration.

class DataImporter {
    /*
     DataImporter is a class to import data from an external file.
     The class is assumed to take a nontrivial amount of time to initialize.
     */
    var filename = "data.txt"
    // the DataImporter class would provide data importing functionality here
}
 
class DataManager {
    lazy var importer = DataImporter()
    var data = [String]()
    // the DataManager class would provide data management functionality here
}
 
print(manager.importer.filename)
// the DataImporter instance for the importer property has now been created
// Prints "data.txt"

Computed Properties

Computed properties which do not actually store a value. Instead, they provide a getter and an optional setter to retrieve and set other properties and values indirectly.

struct Point {
    var x = 0.0, y = 0.0
}
struct Size {
    var width = 0.0, height = 0.0
}
struct Rect {
    var origin = Point()
    var size = Size()
    var center: Point {
        get {
            let centerX = origin.x + (size.width / 2)
            let centerY = origin.y + (size.height / 2)
            return Point(x: centerX, y: centerY)
        }
        set(newCenter) {
            origin.x = newCenter.x - (size.width / 2)
            origin.y = newCenter.y - (size.height / 2)
        }
    }
}
var square = Rect(origin: Point(x: 0.0, y: 0.0),
                  size: Size(width: 10.0, height: 10.0))
let initialSquareCenter = square.center
square.center = Point(x: 15.0, y: 15.0)
print("square.origin is now at (\(square.origin.x), \(square.origin.y))")
// Prints "square.origin is now at (10.0, 10.0)"

Shorthand Setter Declaration

computed property’s setter does not define a name for the new value to be set, a default name of newValue is used.

struct AlternativeRect {
    var origin = Point()
    var size = Size()
    var center: Point {
        get {
            let centerX = origin.x + (size.width / 2)
            let centerY = origin.y + (size.height / 2)
            return Point(x: centerX, y: centerY)
        }
        set {
            origin.x = newValue.x - (size.width / 2)
            origin.y = newValue.y - (size.height / 2)
        }
    }
}

Read-Only Computed Properties

A computed property with a getter but no setter is known as a read-only computed property. A read-only computed property always returns a value, and can be accessed through dot syntax

struct Cuboid {
    var width = 0.0, height = 0.0, depth = 0.0
    var volume: Double {
        return width * height * depth
    }
}
let fourByFiveByTwo = Cuboid(width: 4.0, height: 5.0, depth: 2.0)
print("the volume of fourByFiveByTwo is \(fourByFiveByTwo.volume)")
// Prints "the volume of fourByFiveByTwo is 40.0"

Property Observers

Property observers observe and respond to changes in a property’s value. Property observers are called every time a property’s value is set, even if the new value is the same as the property’s current value.

option to define either or both of these observers on a property:
  • willSet is called just before the value is stored.

  • didSet is called immediately after the new value is stored.

If you implement a willSet observer, it’s passed the new property value as a constant parameter. You can specify a name for this parameter as part of your willSet implementation.

If you don’t write the parameter name and parentheses within your implementation, the parameter is made available with a default parameter name of newValue .

If you implement a didSet observer, it’s passed a constant parameter containing the old property value. You can name the parameter or use the default parameter name of oldValue . If you assign a value to a property within its own didSet observer, the new value that you assign replaces the one that was just set.

Type Properties

Instance properties are properties that belong to an instance of a particular type. Every time you create a new instance of that type, it has its own set of property values, separate from any other instance.

Type Property Syntax

You define type properties with the static keyword. For computed type properties for class types, use the class keyword instead to allow subclasses to override the superclass’s implementation.

struct SomeStructure {
    static var storedTypeProperty = "Some value."
    static var computedTypeProperty: Int {
        return 1
    }
}
enum SomeEnumeration {
    static var storedTypeProperty = "Some value."
    static var computedTypeProperty: Int {
        return 6
    }
}
class SomeClass {
    static var storedTypeProperty = "Some value."
    static var computedTypeProperty: Int {
        return 27
    }
    class var overrideableComputedTypeProperty: Int {
        return 107
    }
}

swift4 语法备忘- Control Flow

For-In Loops

You use the for-in loop to iterate over a sequence, such as items in an array, ranges of numbers, or characters in a string.

let names = ["Anna", "Alex", "Brian", "Jack"]
for name in names {
    print("Hello, \(name)!")
}
// Hello, Anna!
// Hello, Alex!
// Hello, Brian!
// Hello, Jack!

You can also iterate over a dictionary to access its key-value pairs. Each item in the dictionary is returned as a (key, value) tuple when the dictionary is iterated, and you can decompose the (key, value) tuple’s members as explicitly named constants for use within the body of the for-in loop.

Dictionary are inherently unordered

let numberOfLegs = ["spider": 8, "ant": 6, "cat": 4]
for (animalName, legCount) in numberOfLegs {
    print("\(animalName)s have \(legCount) legs")
}
// ants have 6 legs
// spiders have 8 legs
// cats have 4 legs

You can also use for-in loops with numeric ranges. This example prints the first few entries in a five-times table:

for index in 1...5 {
    print("\(index) times 5 is \(index * 5)")
}
// 1 times 5 is 5
// 2 times 5 is 10
// 3 times 5 is 15
// 4 times 5 is 20
// 5 times 5 is 25

If you don’t need each value from a sequence, you can ignore the values by using an underscore in place of a variable name

let base = 3
let power = 10
var answer = 1
for _ in 1...power {
    answer *= base
}
print("\(base) to the power of \(power) is \(answer)")
// Prints "3 to the power of 10 is 59049"

use stride(from:to:by:) to determinate loop step

let minutes = 60
let minuteInterval = 5

for tickMark in stride(from: 0, to: minutes, by: minuteInterval) {
    // render the tick mark every 5 minutes (0, 5, 10, 15 ... 45, 50, 55)
    print(tickMark)
}

使用styled-components的用法备忘

  1. overide antd component default style
import React from 'react';
import Button from 'antd/lib/button';
import styled from "styled-components";

const StyledButton = styled(Button)`
  color: palevioletred;
  font-weight: normal;
  :focus {
    color: palevioletred;
    border-color: palevioletred;
  }
  :hover {
    color: palevioletred;
    border-color: palevioletred;
  }
  &.ant-btn-clicked:after {
    content: '';
    position: absolute;
    top: -1px;
    left: -1px;
    bottom: -1px;
    right: -1px;
    border-radius: inherit;
    border: 0 solid palevioletred;
    opacity: 0.4;
    -webkit-animation: buttonEffect 0.4s;
    animation: buttonEffect 0.4s;
    display: block;
  }
`;
  1. show component displayname

typescript-plugin-styled-components is a plugin for TypeScript that gives you a nicer debugging experience

// 1. import default from the plugin module
const createStyledComponentsTransformer = require('typescript-plugin-styled-components').default;

// 2. create a transformer;
// the factory additionally accepts an options object which described below
const styledComponentsTransformer = createStyledComponentsTransformer({ displayName: true});

// 3. add getCustomTransformer method to the loader config
var config = {
    ...
    module: {
        rules: [
            {
                test: /\.tsx?$/,
                loader: 'ts-loader',
                options: {
                    ... // other loader's options
                    getCustomTransformers: () => ({ before: [styledComponentsTransformer] })
                }
            }
        ]
    }
    ...
};


interface Options {
    getDisplayName(filename: string, bindingName: string | undefined): string | undefined;
    identifiers: CustomStyledIdentifiers;
    ssr: boolean;
    displayName: boolean;
    minify: boolean;
}

Yarn学习笔记

Yarn 简介

Yarn 是Node.js平台的代码包管理器。类似于知名的npm包管理器,实际是npm客户端。

yarn:/jɑːn/,纱线,奇谈,故事。

Facebook 推出了一款名叫 Yarn 的包管理器,声称比现有的 npm 客户端更快,更可靠,更安全。Yarn可以将安装时间从数分钟减少至几秒钟。Yarn还兼容npm注册表,但包安装方法有所区别。其使用了lockfiles和一个决定性安装算法,能够为参与一个项目的所有用户维持相同的节点模块(node_modules)目录结构,有助于减少难以追踪的bug和在多台机器上复制。

为什么放弃 npm

npm 的速度实在实在太慢了。它或许没有什么太大的功能缺陷,但是真的,它太慢了。仅仅因为这个,和 yarn 一对比,几乎没有多少人能回去。因为 yarn 不仅飞快、扩展了功能,而且还保证了对 npm 的兼容。

安装Yarn

// Linux
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
echo "deb http://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
sudo apt-get update && sudo apt-get install yarn

//MacOS
brew update 
brew install yarn

// Windows
下载msi安装包。确保Node.js已经安装并可用。
下载Yarn安装包 https://yarnpkg.com/en/docs/install#windows-tab

最后检查安装是否成功:

yarn --version

国内加速,换上taobao的registry,

 yarn config set registry "https://registry.npm.taobao.org"

常用使用流程

1. 初始化项目

yarn init

2. 初始化项目

添加依赖

// 把包添加到package.json里的dependencies,也同时会更新yarn.lock

yarn add [package]   # 通过名称添加一个依赖包
yarn add [package]@[version]   #“包名@版本号”格式
yarn add [package]@[tag]   #“包名@标签”格式

yarn add --dev/-D #添加到devDependencies
yarn add --peer/-P #添加到peerDependencies
yarn add --optional/-O # 添加到optionalDependencies

安装依赖包

# 从package.json里提取所有的依赖并安装,然后生成yarn.lock锁定所有的依赖版本,
# 别人执行yarn install时会根据yarn.lock安装依赖,保证不同的电脑安装的依赖目录结构完全一致。
yarn install

# 有且仅有一个依赖的版本被允许,多依赖会出现一个交互式窗口,让使用者选择某一个版本安装
yarn install --flat 

#强制重新下载所有的依赖包
yarn install --force 

#只下载dependencies下的依赖
yarn install --production 

更新依赖包

yarn upgrade [package]
yarn upgrade [package]@[version]
yarn upgrade [package]@[tag]

删除依赖包

// 删除某个依赖,并自动更新package.json和yarn.lock文件
yarn remove [package]

全局命令

在yarn命令前加一个global修饰,可以将命令变为全局的,支持的命令有 add,bin,ls,remove,upgrade

例如 , npm install gulp -g 可以用yarn global add gulp来替代

NPM和Yarn命令对比

npm 命令 Yarn 命令 说明
npm install yarn install 安装依赖包
(N/A) yarn install --flat 单版本模式
(N/A) yarn install --har 生成har文件,记录安装时网络请求性能
(N/A) yarn install --no-lockfile 不读写lockfile方式
(N/A) yarn install --pure-lockfile 不生成yarn.lock文件
npm install [package] (N/A) 安装依赖
npm install --save [package] yarn add [package] 添加生产模式依赖到项目
npm install --save-dev [package] yarn add [package] [--dev/-D] 添加开发模式的依赖
(N/A) yarn add [package] [--peer/-P] 对等模式添加依赖,发布/分享项目时的依赖
npm install --save-optional [package] yarn add [package] [--optional/-O]
npm install --save-exact [package] yarn add [package] [--exact/-E]
(N/A) yarn add [package] [--tilde/-T]
npm install --global [package] yarn global add [package]
npm rebuild yarn install --force
npm uninstall [package] (N/A)
npm uninstall --save [package] yarn remove [package]
npm uninstall --save-dev [package] yarn remove [package]
npm uninstall --save-optional [package] yarn remove [package]
npm cache clean yarn cache clean
rm -rf node_modules && npm install yarn upgrade

理解 generator 和co 的特性

koa 是由 TJ 利用 generator 开发的 web 框架。最近在写 koa的时候,顺便深入去了解 generator 与 co。

Generator介绍

 function* gen(){
    yield 1;
    yield 2;
}

var g = gen();  
console.log(g.next());//{ value: 1, done: false }  
console.log(g.next());//{ value: 2, done: false }  
console.log(g.next());//{ value: undefined, done: true }  
console.log(g.next());//Error: Generator has already finished 

上面我们可以看到

  • Generator 不同于普通函数,是可以暂停执行的,所以函数名之前要加星号作为区别。
  • Generator 被调用后会返回一个内部指针(即遍历器 )g 。这是 Generator 函数不同于普通函数的另一个地方,即执行它不会返回结果,返回的是指针对象。调用指针 g 的 next 方法,会移动内部指针(即执行异步任务的第一段),指向第一个遇到的 yield
  • 返回的对象中存在一个 next 函数,调用 next 会返回 yield运算的结果对象,并停止。再次调用会在下一个 yield 处停止,当所有的 yield 被执行完,调用 next 函数会返回 { value: undefined, done: true }。再次调用会报错

Generator 与异步

  • 函数可以暂停执行和恢复执行。
  • 函数体内外的数据交换和错误处理机制。

数据交换

function* gen(x){
  var y = yield x + 2;
  return y;
}

var g = gen(1);
g.next() // { value: 3, done: false }
g.next(2) // { value: 2, done: true }

第一个 next 方法的 value 属性,返回表达式 x + 2 的值(3)。
第二个 next 方法带有参数2,这个参数可以传入 Generator 函数,
作为上个阶段异步任务的返回结果,被函数体内的变量 y 接收。
因此,这一步的 value 属性,返回的就是2(变量 y 的值)。

Generator 函数内部还可以部署错误处理代码,捕获函数体外抛出的错误。

function* gen(x){
  try {
    var y = yield x + 2;
  } catch (e){ 
    console.log(e);
  }
  return y;
}

var g = gen(1);
g.next();
g.throw('出错了';
// 出错了

使用指针对象的 throw 方法抛出的错误,可以被函数体内的 try ... catch 代码块捕获。

利用Generator以上这些特点可对异步的代码进行改写,使得更像同步一样便于理解

var fetch = require('node-fetch');

function* gen(){
  var url1 = 'https://api.github.com/users/repo';
  var url2 = 'https://api.github.com/users/issue';
  var repo = yield fetch(url1); // promise
  var issue = yield fetch(url2);
  console.log(repo, issue);
}

function run(GenFunc) {
  return function() {
    var args = Array.slice.call(arguments)
    var gen = GenFunc()
    next()
    function next(args) { // 传入args
      if (gen.next) {
        var ret = gen.next(args) // 使用args
        if (!ret.done) {
             ret.value(next)
        }
      }
    }
  }
}

run(gen)

从上面的代码可以看出, generator函数需要连续运行下去,需要做到以下几点

  • yield 后面的内容需要返回一个异步函数,这样我们才可进一步封装异步处理的逻辑。
  • 需要可以传入参数,这样才可以处理多个异步
  • 需要一个递归调用,这样才可以持续调用 next,同时根据 next 返回对象中的done属性停止逻辑
    需要考虑错误处理

co源码解析

co的API一般用法,

Returns a promise that resolves a generator, generator function, or any function that returns a generator.

co(fn*).then( )

co(function* () {
  return yield Promise.resolve(true);
}).then(function (val) {
  console.log(val);
}, function (err) {
  console.error(err.stack);
});

co核心代码不多,设计的非常精妙

function co(gen) {
  var ctx = this;
  var args = slice.call(arguments, 1);

  // we wrap everything in a promise to avoid promise chaining,
  // which leads to memory leak errors.
  // see https://github.com/tj/co/issues/180

  return new Promise(function(resolve, reject) {
    if (typeof gen === 'function') gen = gen.apply(ctx, args);
    if (!gen || typeof gen.next !== 'function') return resolve(gen);

   //主要就是走下面的onFulfilled方法,这个方法返回的是一个promise(resolve或者reject)
    onFulfilled();

    /**
     * @param {Mixed} res
     * @return {Promise}
     * @api private
     */

    function onFulfilled(res) {
      var ret;
      try {
        //调用第一次next方法
        ret = gen.next(res);
      } catch (e) {
        //出错了直接reject出去
        return reject(e);
      }
       //将第一次的结果({done:true,value:{}}) 传入内部方法next
      next(ret);
      return null;
    }

    /**
     * @param {Error} err
     * @return {Promise}
     * @api private
     */

    function onRejected(err) {
      var ret;
      try {
        ret = gen.throw(err);
      } catch (e) {
        return reject(e);
      }
      next(ret);
    }

    /**
     * Get the next value in the generator,
     * return a promise.
     *
     * @param {Object} ret
     * @return {Promise}
     * @api private
     */

    function next(ret) {
      //如果done为true的话,代表执行结束,返回一个resolve的promise
      if (ret.done) return resolve(ret.value);

      //还没执行完,就将ret.value转换成一个promise
      var value = toPromise.call(ctx, ret.value);

      //如果成功转化为了promise,就在这个promise执行完了再调用onFulfilled方法
      if (value && isPromise(value)) return value.then(onFulfilled, onRejected);

      return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
        + 'but the following object was passed: "' + String(ret.value) + '"'));
    }
  });
}

electron 开发相关

electron-builder 在构建的时候仅仅把production的依赖部分重新rebuild而已,所以有时需要两个package.json 文件的结构

  1. 对于开发环境的package.json
    package.json 文件处在你项目中的根目录中,在这文件中你要声明开发环境的依赖devDependencies 和一些构建相关的脚本
  2. 对于应用的package.json
    package.jsonapp目录下,在该文件中你需要声明你程序的依赖dependencies,这个目录的依赖与应用最终发布版本相关

为什么要这样设计

  1. 一些npm 的模块并不是由单纯的js写的,有些和C相关的,这些模块需要根据不同的平台进行编译,一些用在应用相关的模块需要根据electron runtime进行编译
  2. 无需特意的指定那些文件是包含在项目里面,因为开发相关的模块都处于app目录以外

使用extract-text-webpack-plugin 无法刷新更改后的样式

在webpack中 可以对组件进行hot reload
但对于一些样式文件,如sass,less , 虽然是已经rebuild了,
但前端依旧没更新。
问题可以在前端入口加上相应的hook

   const hotEmitter = require('webpack/hot/emitter');

   hotEmitter.on('webpackHotUpdate', () => {
       document.querySelectorAll('link[href][rel=stylesheet]').forEach((link) => {
           const nextStyleHref = link.href.replace(/(\?\d+)?$/, `?${Date.now()}`);
           const newLink = link.cloneNode();
           newLink.href = nextStyleHref;

           link.parentNode.appendChild(newLink);
           setTimeout(() => {
               link.parentNode.removeChild(link);
           }, 2000);
       });
   })

Swift 中使用weak避免对象循环引用

项目中经常会在Closure使用unownedweak 修饰 self 对象避免循环引用所带来的坑

Closure里面会用到self的地方,一般都是这样处理

{[unowned self] in }

但是在一个网络请求的Closure回调里面,这样后来发现在这个Closure执行之前, self 已经被释放了

{[weak self] in 
  if let weSelf = self{
      执行代码
}
}

swift 会使用ARC(Automatic Reference Counting)帮我们打理内存,但是在有些时候,尤其是对象间存在互相引用的时候,就会由于reference cycle导致内存无法释放,而我们也需要时刻保持清醒,来避免内存泄露

ARC是如何工作的呢?

Swift 使用“引用计数(reference count)”来管理类对象的生命周期,避免类对象在“仍被使用”的时候被意外释放。当一个对象的reference count为0时,Swift会立即删除该对象。

class Person { 
	let name: String
	init(name: String) { 
	   self.name = name print("\(name) is being initialized.")
	}
	deinit { 
	   print("\(name) is being deinitialized.") 
	}
}

当执行以下的流程代码时:

var ref1: Person?
var ref2: Person?

ref1 = Person(name: "Mars") // count = 1
// Mars is being initialized.

ref2 = ref1 // count = 2

ref2 = nil // count = 1

ref1 = nil // count = 0
// Mars is being deinitialized.

Reference cycle(循环引用)又是怎么发生的呢?

对于Person这样的单个对象,ARC 可以很好的默默为我们工作。但是,当不同类对象之间存在相互引用时,指向彼此的是强引用,就会导致reference cycle, ARC无法释放它们之中的任何一个。

再看看下面的例子:

class Person { 
	let name: String
	init(name: String) { 
	   self.name = name print("\(name) is being initialized.")
	}
	deinit { 
	   print("\(name) is being deinitialized.") 
	}
}

class Apartment { 
 let unit: String
 var tenant: Person?
 init(unit: String) { 
    self.unit = unit
    print("Apartment \(unit) is being initialized.")
 }
 deinit { 
    print("Apartment \(unit) is being deinitialized.") 
  }
}

执行以下的代码

var mars: Person? = Person(name: "Mars") // count = 1
// Mars is being initialized

var apt: Apartment? = Apartment(unit: "11") // count = 1
// Apartment 11 is being initialized

mars!.apartment = apt11
// mars.count = 2
apt!.tenant = mars

// Set mars and apartment to nil
mars = nil
apt = nil

尽管我们把marsapt设置为nilPersonApartmetndeinit也不会被调用了。因为它们的两个memberapartmenttenant)是一个强引用,指向了彼此,让对象仍旧“存活”在内存里。但是,marsapt已经被设置成nil,我们也已经无能为力了。这就是类对象之间的reference cycle

Reference cycle的解决方案

  1. 当class member允许为nil时

ApartmentPerson 中引起reference cycle数据成员都允许为nil,对于这种情况,我们可以像下面这样使用weak来解决 reference cycle

weak var name: Type

class Apartment { 
  let unit: String
  weak var tenant: Person? 
// omit for simplicity...
}

var mars: Person? = Person(name: "Mars")
var apt: Apartment? = Apartment(unit: "11", owner: mars!)
mars!.apartment = apt
apt!.tenant = mars
mars = nil //由于已经没有strong reference指向**mars**,于是**mars**就被**ARC**释放了。
apt = nil // 这时,也没有任何strong reference指向apt了,它也会被ARC释放。

那么,我们是用weak还是unowned呢

在闭包和捕获的实例总是相互引用并且同时销毁时,将闭包内的捕获定义为unowned(无主引用)。
相反的,在被捕获的引用可能会变为nil时,将闭包内的捕获定义为 weak(弱引用)。

那什么情况下需要用到weak呢?

class ViewController: UIViewController {
    var workItem = DispatchWorkItem(block: { [weak self] in
        guard let strongSelf = self else { return }
        strongSelf.updateUI()
    })
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        DispatchQueue.main.asyncAfter(deadline: .now() + 2.33, execute: workItem)
    }
    
    func updateUI() {
        // ...
    }
}

上面是个延时操作的例子,如果声明为unowned(无主引用),在还没有到达延迟的时间的时候,我们 pop 掉这个 ViewController 的话,程序就会 crash。因为无主引用是非可选类型,pop 掉这个 ViewController 后,闭包中捕获的 self (也就是ViewController的实例)已经被销毁了,这时候再访问被销毁的实例,程序肯定会奔溃的。

所以这里应该声明为weak(弱引用),弱引用总是可选类型,当引用的实例被销毁后,弱引用的值会自动置为 nil,并且我们可以在闭包内检查它们是否存在。

useMemo 和 useCallback 使用备忘

性能优化总是会有成本,但并不总是带来好处。
下面谈谈 useMemouseCallback 的成本和收益。

说说useCallback

结合下面的例子来讲讲

function CandyDispenser() {
  const initialCandies = ['snickers', 'skittles', 'twix', 'milky way']
  const [candies, setCandies] = React.useState(initialCandies)
  const dispense = candy => {
    setCandies(allCandies => allCandies.filter(c => c !== candy))
  }
  return (
    <div>
      <h1>Candy Dispenser</h1>
      <div>
        <div>Available Candy</div>
        {candies.length === 0 ? (
          <button onClick={() => setCandies(initialCandies)}>refill</button>
        ) : (
          <ul>
            {candies.map(candy => (
              <li key={candy}>
                <button onClick={() => dispense(candy)}>grab</button> {candy}
              </li>
            ))}
          </ul>
        )}
      </div>
    </div>
  )
}

当被问到如何优化以上代码时,
很多人会想到用 React.useCallback 里包裹 dispense 函数

const dispense = React.useCallback(candy => {
  setCandies(allCandies => allCandies.filter(c => c !== candy))
}, [])

但实际情况是 使用原来的代码性能会更好,
如果你选择的是 useCallback,再好好思考下。
为什么使用了useCallback 后性能反而性能不如预期呢?!

很多情况使用 React.useCallback 可以用来提高性能,并且“内联函数可能会对性能造成问题”,那么为啥不使用 usecallCallback变得更好的。useCallback版本做了更多的工作之外,它们完全相同。 我们不仅需要定义函数,还要定义一个数组([])并调用 React.useCallback,它本身会设置属性和运行逻辑表达式等。

在组件的第二次渲染中,原来的 dispense 函数被垃圾收集(释放内存空间),然后创建一个新的 dispense 函数。 但是使用 useCallback 时,原来的 dispense 函数不会被垃圾收集,并且会创建一个新的 dispense 函数,所以从内存的角度来看,这会变得更糟。

作为一个相关的说明,如果你有其它依赖,那么React很可能会挂起对前面函数的引用,因为 memoization 通常意味着我们保留旧值的副本,以便在我们获得与先前给出的相同依赖的情况下返回。 特别聪明的你会注意到,这意味着React还必须挂在对这个等式检查依赖项的引用上

再说说useMemo

useMemo 类似于 useCallback,除了它允许你将 memoization 应用于任何值类型(不仅仅是函数)。 它通过接受一个返回值的函数来实现这一点,然后只在需要检索值时调用该函数(通常这只有在每次渲染中依赖项数组中的元素发生变化时才会发生一次)

如果不想在每次渲染时初始化那个 initialCandies 数组, 可以用 useMemo 进行调整

- const initialCandies = ['snickers', 'skittles', 'twix', 'milky way']
+ const initialCandies = React.useMemo(
+  () => ['snickers', 'skittles', 'twix', 'milky way'],
+  [],
+ )

可以避免那个问题,但是节省的成本是如此之小,以至于换来使代码更加复杂的成本是不值得的。实际上,这里使用useMemo 也可能会更糟,因为我们再次进行了函数调用,并且代码会执行属性赋值等。

什么时候使用 useMemo 和 useCallback

这两个 hooks 内置于 React 都有特别的原因:

  1. 引用相等
  2. 昂贵的计算

Git常用命令集合

Git是一个“分布式版本管理工具”,简单的理解版本管理工具:大家在写东西的时候都用过“回撤”这个功能,但是回撤只能回撤几步,假如想要找回我三天之前的修改,光用“回撤”是找不回来的。而“版本管理工具”能记录每次的修改,只要提交到版本仓库,你就可以找到之前任何时刻的状态(文本状态)。

下面的内容就是列举了常用的Git命令和一些小技巧,可以通过"页面内查找"的方式进行快速查询:Ctrl/Command+f

开卷必读

如果之前未使用过Git,可以学习廖老师的免费Git教程入门

  1. 一定要先测试命令的效果后,再用于工作环境中,以防造成不能弥补的后果!到时候别拿着砍刀来找我
  2. 所有的命令都在git version 2.7.4 (Apple Git-66)下测试通过
  3. 统一概念:
    • 工作区:改动(增删文件和内容)
    • 暂存区:输入命令:git add 改动的文件名,此次改动就放到了‘暂存区’
    • 本地仓库(简称:本地):输入命令:git commit 此次修改的描述,此次改动就放到了’本地仓库’,每个commit,我叫它为一个‘版本’。
    • 远程仓库(简称:远程):输入命令:git push 远程仓库,此次改动就放到了‘远程仓库’(GitHub等)
    • commit-id:输出命令:git log,最上面那行commit xxxxxx,后面的字符串就是commit-id

目录

展示帮助信息

git help -g

回到远程仓库的状态

抛弃本地所有的修改,回到远程仓库的状态。

git fetch --all && git reset --hard origin/master

重设第一个commit

也就是把所有的改动都重新放回工作区,并清空所有的commit,这样就可以重新提交第一个commit了

git update-ref -d HEAD

展示工作区和暂存区的不同

输出工作区暂存区的different(不同)。

git diff

还可以展示本地仓库中任意两个commit之间的文件变动:

git diff <commit-id> <commit-id>

展示暂存区和最近版本的不同

输出暂存区和本地最近的版本(commit)的different(不同)。

git diff --cached

展示暂存区、工作区和最近版本的不同

输出工作区暂存区 和本地最近的版本(commit)的different(不同)。

git diff HEAD

快速切换分支

git checkout -

删除已经合并到master的分支

git branch --merged master | grep -v '^\*\|  master' | xargs -n 1 git branch -d

展示本地分支关联远程仓库的情况

git branch -vv

关联远程分支

关联之后,git branch -vv就可以展示关联的远程分支名了,同时推送到远程仓库直接:git push,不需要指定远程仓库了。

git branch -u origin/mybranch

或者在push时加上-u参数

git push origin/mybranch -u

列出所有远程分支

-r参数相当于:remote

git branch -r

列出本地和远程分支

-a参数相当于:all

git branch -a

创建并切换到本地分支

git checkout -b <branch-name>

创建并切换到远程分支

git checkout -b <branch-name> origin/<branch-name>

删除本地分支

git branch -d <local-branchname>

删除远程分支

git push origin --delete <remote-branchname>

或者

git push origin :<remote-branchname>

重命名本地分支

git branch -m <new-branch-name>

查看标签

git tag

展示当前分支的最近的tag

git describe --tags --abbrev=0

本地创建标签

git tag <version-number>

默认tag是打在最近的一次commit上,如果需要指定commit打tag:

$ git tag -a <version-number> -m "v1.0 发布(描述)" <commit-id>

推送标签到远程仓库

首先要保证本地创建好了标签才可以推送标签到远程仓库:

git push origin <local-version-number>

一次性推送所有标签,同步到远程仓库:

git push origin --tags

删除本地标签

git tag -d <tag-name>

删除远程标签

删除远程标签需要先删除本地标签,再执行下面的命令:

git push origin :refs/tags/<tag-name>

切回到某个标签

一般上线之前都会打tag,就是为了防止上线后出现问题,方便快速回退到上一版本。下面的命令是回到某一标签下的状态:

git checkout -b branch_name tag_name

放弃工作区的修改

git checkout <file-name>

放弃所有修改:

git checkout .

回到某一个commit的状态,并重新增添一个commit

git revert <commit-id>

回到某个commit的状态,并删除后面的commit

和revert的区别:reset命令会抹去某个commit id之后的所有commit

git reset <commit-id>

修改上一个commit的描述

git commit --amend

查看commit历史

git log

查看某段代码是谁写的

blame的意思为‘责怪’,你懂的。

git blame <file-name>

显示本地执行过git命令

就像shell的history一样

git reflog

修改作者名

git commit --amend --author='Author Name <[email protected]>'

修改远程仓库的url

git remote set-url origin <URL>

增加远程仓库

git remote add origin <remote-url>

列出所有远程仓库

git remote

查看两个星期内的改动

git whatchanged --since='2 weeks ago'

把A分支的某一个commit,放到B分支上

这个过程需要cherry-pick命令,参考

git checkout <branch-name> && git cherry-pick <commit-id>

给git命令起别名

简化命令

git config --global alias.<handle> <command>

比如:git status 改成 git st,这样可以简化命令

git config --global alias.st status

存储当前的修改,但不用提交commit

详解可以参考廖雪峰老师的git教程

git stash

保存当前状态,包括untracked的文件

untracked文件:新建的文件

git stash -u

展示所有stashes

git stash list

回到某个stash的状态

git stash apply <stash@{n}>

回到最后一个stash的状态,并删除这个stash

git stash pop

删除所有的stash

git stash clear

从stash中拿出某个文件的修改

git checkout <stash@{n}> -- <file-path>

展示所有tracked的文件

git ls-files -t

展示所有untracked的文件

git ls-files --others

展示所有忽略的文件

git ls-files --others -i --exclude-standard

强制删除untracked的文件

可以用来删除新建的文件。如果不指定文件文件名,则清空所有工作的untracked文件。clean命令,注意两点

  1. clean后,删除的文件无法找回
  2. 不会影响tracked的文件的改动,只会删除untracked的文件
git clean <file-name> -f

强制删除untracked的目录

可以用来删除新建的目录,注意:这个命令也可以用来删除untracked的文件。详情见上一条

git clean <directory-name> -df

展示简化的commit历史

git log --pretty=oneline --graph --decorate --all

把某一个分支到导出成一个文件

git bundle create <file> <branch-name>

从包中导入分支

新建一个分支,分支内容就是上面git bundle create命令导出的内容

git clone repo.bundle <repo-dir> -b <branch-name>

执行rebase之前自动stash

git rebase --autostash

从远程仓库根据ID,拉下某一状态,到本地分支

git fetch origin pull/<id>/head:<branch-name>

详细展示一行中的修改

git diff --word-diff

清除gitignore文件中记录的文件

git clean -X -f

展示所有alias和configs

注意: config分为:当前目录(local)和全局(golbal)的config,默认为当前目录的config

git config --local --list (当前目录)
git config --global --list (全局)

展示忽略的文件

git status --ignored

commit历史中显示Branch1有的,但是Branch2没有commit

git log Branch1 ^Branch2

在commit log中显示GPG签名

git log --show-signature

删除全局设置

git config --global --unset <entry-name>

新建并切换到新分支上,同时这个分支没有任何commit

相当于保存修改,但是重写commit历史

git checkout --orphan <branch-name>

展示任意分支某一文件的内容

git show <branch-name>:<file-name>

clone下来指定的单一分支

git clone -b <branch-name> --single-branch https://github.com/user/repo.git

忽略某个文件的改动

关闭 track 指定文件的改动,也就是 Git 将不会在记录这个文件的改动

git update-index --assume-unchanged path/to/file

恢复 track 指定文件的改动

git update-index --no-assume-unchanged path/to/file

忽略文件的权限变化

不再将文件的权限变化视作改动

git config core.fileMode false

展示本地所有的分支的commit

最新的放在最上面

git for-each-ref --sort=-committerdate --format='%(refname:short)' refs/heads/

在commit log中查找相关内容

通过grep查找,given-text:所需要查找的字段

git log --all --grep='<given-text>'

把暂存区的指定file放到工作区中

git reset <file-name>

强制推送

git push -f <remote-name> <branch-name>

⬆ 返回顶部

swift4语法备忘- Class and Structures

Classes and Structures

Concept

Classes and structures are general-purpose, flexible constructs that become the building blocks of your program’s code. You define properties and methods to add functionality to your classes and structures by using exactly the same syntax as for constants, variables, functions.

Comparing Classes and Structures

Classes and structures things in common.

  • Define properties to store values

  • Define methods to provide functionality

  • Define subscripts to provide access to their values using subscript syntax

  • Define initializers to set up their initial state

  • Be extended to expand their functionality beyond a default implementation

  • Conform to protocols to provide standard functionality of a certain kind

Classes have additional capabilities that structures do not:

  • Inheritance enables one class to inherit the characteristics of another.

  • Type casting enables you to check and interpret the type of a class instance at runtime.

  • Deinitializers enable an instance of a class to free up any resources it has assigned.

  • Reference counting allows more than one reference to a class instance.

Syntax

class SomeClass {
    // class definition goes here
}
struct SomeStructure {
    // structure definition goes here
}


struct Resolution {
    var width = 0
    var height = 0
}

class VideoMode {
    var resolution = Resolution()
    var interlaced = false
    var frameRate = 0.0
    var name: String?
}

Class and Structure Instances

Syntax for creating instances is very similar for both structures and classes:

let someResolution = Resolution()
let someVideoMode = VideoMode()

Accessing Properties

Access the properties of an instance using dot syntax. In dot syntax, you write the property name immediately after the instance name, separated by a period (.), without any spaces:

print("The width of someResolution is \(someResolution.width)")
// Prints "The width of someResolution is 0"

someVideoMode.resolution.width = 1280

print("The width of someVideoMode is now \(someVideoMode.resolution.width)")
// Prints "The width of someVideoMode is now 1280"

Memberwise Initializers for Structure Types

All structures have an automatically-generated memberwise initializer, which you can use to initialize the member properties of new structure instances. Initial values for the properties of the new instance can be passed to the memberwise initializer by name:

let vga = Resolution(width: 640, height: 480)

Class instances do not receive a default memberwise initializer.

Structures and Enumerations Are Value Types

value type is a type whose value is copied when it is assigned to a variable or constant, or when it is passed to a function.

Any structure and enumeration instances you create—and any value types they have as properties—are always copied when they are passed around in your code.

Classes Are Reference Types

Reference types are not copied when they are assigned to a variable or constant, or when they are passed to a function. Rather than a copy, a reference to the same existing instance is used instead.

Identity Operators

Swift provides two identity operators:

  • Identical to (===)

  • Not identical to (!==)

Identical to means that two constants or variables of class type refer to exactly the same class instance.

Equal to means that two instances are considered “equal” or “equivalent” in value, for some appropriate meaning of “equal”, as defined by the type’s designer.

When should you choose to use Structures

Consider creating a structure when one or more of these conditions apply:

  • The structure’s primary purpose is to encapsulate a few relatively simple data values.

  • It is reasonable to expect that the encapsulated values will be copied rather than referenced when you assign or pass around an instance of that structure.

  • Any properties stored by the structure are themselves value types, which would also be expected to be copied rather than referenced.

  • The structure does not need to inherit properties or behavior from another existing type.

ES5/6 object 方法整理

ES5/6 object 方法整理

Object.assign()

Object.assign() 用来复制源对象的所有可枚举属性复制到目标对象中,并且返回目标对象

Object.assign(target, ...sources)

let target = {name: 'target'};
let source1 = {age: 23};
let source2 = {email: '[email protected]'};
// ...
// let sourceN ={.....};
traget = Object.assign(target, source1, source2);

注意:

  • 如果target中的属性具有相同的key,那么它们将source中的属性覆盖。较后的source的属性也将将类似地覆盖先前的属性。
  • Object.assign() 仅仅把target 中properties可 enumerable 的属性到target对象中

Object.create()

通过指定的原型对象和属性,创建一个新的对象

Object.create(proto, [,. propertiesObject]);

实现原型继承

// Shape - superclass
function Shape() {
  this.x = 0;
  this.y = 0;
}

// superclass method
Shape.prototype.move = function(x, y) {
  this.x += x;
  this.y += y;
  console.info('Shape moved.');
};

// Rectangle - subclass
function Rectangle() {
  Shape.call(this); // call super constructor.
}

// subclass extends superclass
Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;

var rect = new Rectangle();

console.log('Is rect an instance of Rectangle?', rect instanceof Rectangle);// true
console.log('Is rect an instance of Shape?', rect instanceof Shape);// true
rect.move(1, 1); // Outputs, 'Shape moved.'
var a = {a: 1}; 
// a ---> Object.prototype ---> null

var b = Object.create(a);
// b ---> a ---> Object.prototype ---> null
console.log(b.a); // 1 (继承而来)

var c = Object.create(b);
// c ---> b ---> a ---> Object.prototype ---> null

var d = Object.create(null);
// d ---> null
console.log(d.hasOwnProperty); // undefined, 因为 d 没有继承 Object.prototype

Object.freeze()

Object.freeze(obj)

将一个 object“冻住”:不能添加新的属性;不能删除现有的属性;不能修改现有属性,包括属性的 enumerability, configurability 和 writability。这个方法返回一个不可修改的对象,使用语法:

var obj = {
  prop: function() {},
  foo: 'bar'
};

// New properties may be added, existing properties may be changed or removed
obj.foo = 'baz';
obj.lumpy = 'woof';
delete obj.prop;

// Both the object being passed as well as the returned object will be frozen.
// It is unnecessary to save the returned object in order to freeze the original.
var o = Object.freeze(obj);

o === obj; // true
Object.isFrozen(obj); // === true

// Now any changes will fail
obj.foo = 'quux'; // silently does nothing
obj.quaxxor = 'the friendly duck'; // silently doesn't add the property

// ...and in strict mode such attempts will throw TypeErrors
function fail(){
  'use strict';
  obj.foo = 'sparky'; // throws a TypeError
  delete obj.quaxxor; // throws a TypeError
  obj.sparky = 'arf'; // throws a TypeError
}

fail();

// Attempted changes through Object.defineProperty will also throw
Object.defineProperty(obj, 'ohai', { value: 17 }); // throws a TypeError
Object.defineProperty(obj, 'foo', { value: 'eit' }); // throws a TypeError

深度 freeze

// To do so, we use this function.
function deepFreeze(obj) {

  // Retrieve the property names defined on obj
  var propNames = Object.getOwnPropertyNames(obj);

  // Freeze properties before freezing self
  propNames.forEach(function(name) {
    var prop = obj[name];

    // Freeze prop if it is an object
    if (typeof prop == 'object' && prop !== null)
      deepFreeze(prop);
  });

  // Freeze self (no-op if already frozen)
  return Object.freeze(obj);
}

obj2 = {
  internal: {}
};

deepFreeze(obj2);
obj2.internal.a = 'anotherValue';
obj2.internal.a; // undefined

Object.defineProperty()

Object.defineProperty(obj, prop, descriptor)

关于descriptor相关的属性

  • configurable:可配置性,控制着其描述的属性的修改,表示能否修改属性的特性,能否把属性修改为访问器属性,或者能否通过 delete 删除属性从而重新定义属性。默认值为 true。
  • enumerable:可枚举性,表示能否通过 for-in 遍历得到属性。默认值为 true。
  • value: 数据属性,表示属性的值。默认值为 undefined。
  • writable: 可写性,表示能否修改属性的值。默认值为 true。

和两个存取器属性

  • get:在读取属性时调用的函数。只指定 get 则表示属性为只读属性。默认值为 undefined。
  • set:在写入属性时调用的函数。只指定 set 则表示属性为只写属性。默认值为 undefined。
var o = {}; // Creates a new object

// Example of an object property added with defineProperty with a data property descriptor
Object.defineProperty(o, 'a', {
  value: 37,
  writable: true,
  enumerable: true,
  configurable: true
});
// 'a' property exists in the o object and its value is 37

Object.defineProperties()

Object.defineProperties(obj, props)

Object.defineProperty 升级版本,可以一次同时定义多个属性,语法略有不同:

var obj = {};
Object.defineProperties(obj, {
  'property1': {
    value: true,
    writable: true
  },
  'property2': {
    value: 'Hello',
    writable: false
  }
  // etc. etc.
});

Object.keys()

Object.keys(obj)

以数组的形式返回对象中自身可枚举的属性

var arr = ['a', 'b', 'c'];
console.log(Object.keys(arr)); // console: ['0', '1', '2']

// array like object
var obj = { 0: 'a', 1: 'b', 2: 'c' };
console.log(Object.keys(obj)); // console: ['0', '1', '2']

// array like object with random key ordering
var an_obj = { 100: 'a', 2: 'b', 7: 'c' };
console.log(Object.keys(an_obj)); // console: ['2', '7', '100']

// getFoo is property which isn't enumerable
var my_obj = Object.create({}, { getFoo: { value: function() { return this.foo; } } });
my_obj.foo = 1;

console.log(Object.keys(my_obj)); // console: ['foo']

Mozilla

swift4 语法备忘- Closure

Closures

Closures can capture and store references to any constants and variables from the context in which they are defined.

Three Form

  1. Global functions are closures that have a name and do not capture any values.
  2. Nested functions are closures that have a name and can capture values from their enclosing function
  3. Closure expressions are unnamed closures written in a lightweight syntax that can capture values from their surrounding context.

Swift closures enhancement

  1. Inferring parameter and return value types from context
  2. Implicit returns from single-expression closures
  3. Shorthand argument names
  4. Trailing closure syntax

Closure Expression Syntax

General Form of Closure

{ (parameters) -> return type in
    statements
}

Trailing Closures

A trailing closure is written after the function call’s parentheses, even though it is still an argument to the function. When you use the trailing closure syntax, you don’t write the argument label for the closure as part of the function call.

func someFunctionThatTakesAClosure(closure: () -> Void) {
    // function body goes here
}
 
// Here's how you call this function without using a trailing closure:
 
someFunctionThatTakesAClosure(closure: {
    // closure's body goes here
})
 
// Here's how you call this function with a trailing closure instead:
 
someFunctionThatTakesAClosure() {
    // trailing closure's body goes here
}

closure demo

let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]

func backward(_ s1: String, _ s2: String) -> Bool {
    return s1 > s2
}

var reversedNames = names.sorted(by: backward)

// reversedNames is equal to ["Ewa", "Daniella", "Chris", "Barry", "Alex"]

// with closure systax
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
    return s1 > s2
})

// shorten with single line version
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 > s2 } )

// shorten with Inferring Type From Context version
reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )

// shorten with Implicit Returns from Single-Expression
reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )

// shorten with Shorthand Argument Names
reversedNames = names.sorted(by: { $0 > $1 } )

// shorten with Operator Methods
reversedNames = names.sorted(by: >)

// shorten with Trailing Closures
reversedNames = names.sorted() { $0 > $1 }
reversedNames = names.sorted { $0 > $1 } // only argument can omit parentheses()

Capturing Values

A closure can capture constants and variables from the surrounding context in which it is defined. The closure can then refer to and modify the values of those constants and variables from within its body, even if the original scope that defined the constants and variables no longer exists.

In swift, A nested function can capture any of its outer function’s arguments and can also capture any constants and variables defined within the outer function.

@escaping closure means it needs to refer to self explicitly

func makeAnIncrement(_ delta: Int) -> () -> Int {
    var startValue: Int = 0;
    func Inc() -> Int {
        startValue += delta;
        return startValue;
    }
    return Inc
}

let Ten = makeAnIncrement(10);
_ = Ten();
_ = Ten();
let t = Ten();
print(t) // 30


let Twn = makeAnIncrement(20);
_ = Twn();
_ = Twn();
let x = Twn();
print(x) // 60

Closures Are Reference Types

Whenever you assign a function or a closure to a constant or a variable, you are actually setting that constant or variable to be a reference to the function or closure.

Escaping Closures

A closure is said to escape a function when the closure is passed as an argument to the function, but is called after the function returns. When you declare a function that takes a closure as one of its parameters, you can write @escaping before the parameter’s type to indicate that the closure is allowed to escape.

var completionHandlers: [() -> Void] = []

func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
    completionHandlers.append(completionHandler)
}

func someFunctionWithNonescapingClosure(closure: () -> Void) {
    closure()
}
 
class SomeClass {
    var x = 10
    func doSomething() {
        someFunctionWithEscapingClosure { self.x = 100 }
        someFunctionWithNonescapingClosure { x = 200 }
    }
}
 
let instance = SomeClass()
instance.doSomething()
print(instance.x)
// Prints "200"
 
completionHandlers.first?()
print(instance.x)
// Prints "100"

Autoclosures

An autoclosure is a closure that is automatically created to wrap an expression that’s being passed as an argument to a function. It doesn’t take any arguments, and when it’s called, it returns the value of the expression that’s wrapped inside of it. This syntactic convenience lets you omit braces around a function’s parameter by writing a normal expression instead of an explicit closure.

An autoclosure lets you delay evaluation, because the code inside isn’t run until you call the closure

// customersInLine is ["Alex", "Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: () -> String) {
    print("Now serving \(customerProvider())!")
}
serve(customer: { customersInLine.remove(at: 0) } )
// Prints "Now serving Alex!"


// autoclosure version

// customersInLine is ["Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: @autoclosure () -> String) {
    print("Now serving \(customerProvider())!")
}
serve(customer: customersInLine.remove(at: 0))
// Prints "Now serving Ewa!"

Rxjs 使用备忘

Behavior Subject 和 Subject 的区别

Behavior Subject is a type of subject, a subject is a special type of observable so you can subscribe to messages like any other observable

behavior subject 的特性

  • Behavior subject needs an initial value as it must always return a value on subscription even if it hasn’t received a next()
  • Upon subscription, it returns the last value of the subject. A regular observable only triggers when it receives a on next at any point you can retrieve the last value of the subject in a non-observable code using the getValue() method.

举个例子

Behavior Subject

// a is a initial value. if there is a subscription 
// after this, it would get "a" value immediately
let bSubject = new BehaviorSubject("a"); 

bSubject.next("b");

bSubject.subscribe((value) => {
  console.log("Subscription got", value); // Subscription got b, 
                                          // ^ This would not happen 
                                          // for a generic observable 
                                          // or generic subject by default
});

bSubject.next("c"); // Subscription got c
bSubject.next("d"); // Subscription got d

Regular Subject

let subject = new Subject(); 

subject.next("b");

subject.subscribe((value) => {
  console.log("Subscription got", value); // Subscription wont get 
                                          // anything at this point
});

subject.next("c"); // Subscription got c
subject.next("d"); // Subscription got d

webpack常用loader和plugin备忘

css loader 和 style loader

css loader
style loader

Webpack允许像加载任何代码一样加载 CSS, 加载 CSS 需要 css-loaderstyle-loadercss-loader会遍历 CSS 文件,将文件里面的内容转化成CSS-JSON的形式,style-loader 让网页运行时根据require的顺序,依次把CSS-JSON字符串用 <style> </style> 的方式塞到 head 区域内

安装

npm install --save-dev css-loader style-loader

CSS loader 配置

Name Default Description
root / Path to resolve URLs, URLs starting with / will not be translated
modules false Enable/Disable CSS Modules
import true Enable/Disable @import handling
url true Enable/Disable url() handling
minimize false Enable/Disable minification
sourceMap false Enable/Disable Sourcemaps
camelCase false Export Classnames in CamelCase
importLoaders 0 Number of loaders applied before CSS loader

默认情况下CSS 文件中的 @importurl() 都会被解析成像 import 那样,所以需要file-loaderurl-loader 配合使用

  //没开启 CSS Module下
 `url(image.png)` => `require('./image.png')`
 `url(~module/image.png)` => `require('module/image.png')`

CSS 加载策略

根据你的应用,你可能会考略三种策略。另外,你需要考虑把一些基础的 CSS 内联到初始容器中(index.html),这样设置的结构能够在应用下载和执行的时候加载剩下的应用。

  • 所有合并成一个

在你的主入口文件中个,比如 app/main.js 你可以为整个项目加载所有的 CSS:

#app/main.js

import './project-styles.css';
// 其他 JS 代码

  • 懒加载

如果应用中有多个入口文件的,你可以在每个入口点包含各自的 CSS:

#app/main.js

import './style.css';
// 其他 JS 代码

#app/entryA/main.js

import './style.css';
// 其他 JS 代码

#app/entryB/main.js

import './style.css';
// 其他 JS 代码

  • 组件独立样式

你可以根据这个策略为每个组件创建 CSS 文件,可以让组件名和 CSS 中的 class 使用一个命名空间,来避免一个组件中的一些 class 干扰到另外一些组件的 class。

#app/components/MyComponent.css

.MyComponent-wrapper {
  background-color: #EEE;
}

#app/components/MyComponent.jsx

import './MyComponent.css';
import React from 'react';

export default React.createClass({
  render: function () {
    return (
      <div className="MyComponent-wrapper">
        <h1>Hello world</h1>
      </div>
    )
  }
});
  • 内联样式取代 CSS 文件
import React from 'react';

var style = {
  backgroundColor: '#EEE'
};

export default React.createClass({
  render: function () {
    return (
      <div style={style}>
        <h1>Hello world</h1>
      </div>
    )
  }
});

file loader 和 url loader

file loader
url loader

file loader

前面说的, css-loader 会将css中遇到的 url('./xx.jpg') 转成 require('./xx.jpg') 的形式; file-loader的作用就是处理文件加载。
修改文件名,放在输出目录下,并返其对应的 url .

默认修改后的文件名,是文件内容的MD5哈希串。你也可以自定义文件名。比如:

require(“file?name=js/[hash].script.[ext]!./javascript.js”); // => js/0dcbbaa701328a3c262cfd45869e351f.script.js

require(“file?name=html-[hash:6].html!./page.html”); // => html-109fa8.html

require(“file?name=[hash]!./flash.txt”); // => c31e9820c001c9c4a86bce33ce43b679

require(“file?name=[sha512:hash:base64:7].[ext]!./image.png”); // => gdyb21L.png // use sha512 hash instead of md5 and with only 7 chars of base64

require(“file?name=img-[sha512:hash:base64:7].[ext]!./image.jpg”); // => img-VqzT5ZC.jpg // use custom name, sha512 hash instead of md5 and with only 7 chars of base64

require(“file?name=picture.png!./myself.png”); // => picture.png

require(“file?name=[path][name].[ext]?[hash]!./dir/file.png”) // => dir/file.png?e43b20c069c4a01867c31e98cbce33c9 

url-loader

这个加载器的工作方式很像file-loader。只是当文件大小小于限制值时,它可以返回一个Data Url。限制值可以作为查询参数传入。默认不限制。比如:

require(“url?limit=10000!./file.png”); // => 如果”file.png”小于10kb,则转成一个DataUrl

require(“url?mimetype=image/png!./file.png”); // => 指定文件的mimetype (不指定,则根据文件后缀推测.)

JS构造函数和原型对象相关总结

常用的几种对象创建模式

使用new关键字创建

var obj = new Object()
obj.name = 'foo'
obj.age = 24
obj.say = function() {
    console.log(`my name is ${this.name}, age is ${this.age}`)
}

使用字面量创建

var obj = {
    name : "foo",
    age: 24,
    say: function() {
        console.log(`my name is ${this.name}, age is ${this.age}`)
    }
}
// obj 这个对象继承了 Object.prototype 上面的所有属性
// 所以可以这样使用 obj .hasOwnProperty('name').
// hasOwnProperty 是 Object.prototype 的自身属性。
// Object.prototype 的原型为 null。
// 原型链如下:
// o ---> Object.prototype ---> null

工厂模式

function createObj(name, age) {
    var o = new Object();
    o.name = name;
    o.age = age;
    o.say = function() {
        console.log(`my name is ${this.name}, age is ${this.age}`)
    }
    return o;
}
var tom = createObj("tom", 12);
var jack = createObj("jack", 22);

构造函数

function Person(name, age) {
    this.name = name;
    this.age = age;
    this.say = function() {
        console.log(`my name is ${this.name}, age is ${this.age}`)
    }
}
var tom = new Person("tom", 12);
var jack = new Person("jack", 22);

// tom  是生成的对象, 他的自身属性有 'name' 和 'age'.
// 在 tom 被实例化时,tom.[[Prototype]]指向了 Person.prototype.
// tome ---> Person.prototype---> Object.prototype ---> null

注意按照约定构造函数的首字母要大写。
在这里我们创建一个新对象过程如下:

  • 创建一个新的对象,并让 this 指针指向它
  • 将函数的 prototype 对象的所有成员都赋给这个新对象
  • 执行函数体,对这个对象进行初始化操作
  • 返回刚创建的新对象

注意:

  • 如果类中有个 return, 那么就无法返回创建的对象,而返回 return 那句,这样就像没有使用 new 来调用一个函数一样
  • 如果不使用new 调用构造函数,实质上与调用普通的方法函数无异,关键之处在于调用时,所处的作用域为全局作用域,
    this 指向了 window,也就是说,这次调用发生之后,平白无故的为 window 对象增添了两个属性 name 和 age。
    为了解决这种问题,可以在构造函数中检查当前作用域,也就是 this 指向何处
function CreateObj(x,y) {
  if (this instanceof CreateObj) {
    this.age = x;
    this.name = y
  } else {
    return new CreateObj(x,y);
  }
}

使用Object.create

Object.create(O[, Properties])
returns an object with a prototype of O and properties according to Properties (as if called through Object.defineProperties).

作用的效果可等价于

Object.create = function(O) {  
  function F() {};  
  F.prototype = O;  
  return new F();  
}  
var proto = {
  say: function () {
    console.log(`my name is ${this.name}, age is ${this.age}`)
 }};

var p = Object.create(proto);
p.name = 'kate';
p.age = 12;

p.say() //my name is kate age is 12

Spring security Oauth2 学习记录

Oauth2 的使用场景

A厂有一套HTTP接口需要提供给B厂使用,由于是外网环境,那么就需要有一套安全机制保障
oauth2 就可以作为一个方案

相关用户概念

  • Third-party application:第三方应用程序,又称"客户端"(client)

  • HTTP service:HTTP服务提供商,

  • Resource Owner:资源所有者, 又称"用户"(user)

  • User Agent:用户代理,本文中就是指浏览器。

  • Authorization server:认证服务器,即服务提供商专门用来处理认证的服务器。

  • Resource server:资源服务器,即服务提供商存放用户生成的资源的服务器。它与认证服务器,可以是同一台服务器,也可以是不同的服务器。

OAuth 2.0的运行流程

如下图,摘自 RFC 6749

bg2014051203

(A)用户打开客户端以后,客户端要求用户给予授权。

(B)用户同意给予客户端授权。

(C)客户端使用上一步获得的授权,向认证服务器申请令牌。

(D)认证服务器对客户端进行认证以后,确认无误,同意发放令牌。

(E)客户端使用令牌,向资源服务器申请获取资源。

(F)资源服务器确认令牌无误,同意向客户端开放资源。

Gradle入门

Installing Gradle

  1. java development kit
java -version
java version "1.8.0_151"
Java(TM) SE Runtime Environment (build 1.8.0_151-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.151-b12, mixed mode)
  1. install via homebrew
brew install gradle
  1. verifying installation
> gradle -v

------------------------------------------------------------
Gradle 6.6.1
------------------------------------------------------------

Build time:   2020-08-25 16:29:12 UTC
Revision:     f2d1fb54a951d8b11d25748e4711bec8d128d7e3

Kotlin:       1.3.72
Groovy:       2.5.12
Ant:          Apache Ant(TM) version 1.10.8 compiled on May 10 2020
JVM:          14.0.1 (Oracle Corporation 14.0.1+14)
OS:           Mac OS X 10.14.6 x86_64

Building Java Applications Sample

  1. Create a project folder
create a folder for the new project and change directory into it.

$ mkdir demo
$ cd demo
  1. Run the init task
$ gradle init

Select type of project to generate:
  1: basic
  2: application
  3: library
  4: Gradle plugin
Enter selection (default: basic) [1..4] 2

Select implementation language:
  1: C++
  2: Groovy
  3: Java
  4: Kotlin
  5: Scala
  6: Swift
Enter selection (default: Java) [1..6] 3

Select build script DSL:
  1: Groovy
  2: Kotlin
Enter selection (default: Groovy) [1..2] 1

Select test framework:
  1: JUnit 4
  2: TestNG
  3: Spock
  4: JUnit Jupiter
Enter selection (default: JUnit 4) [1..4]

Project name (default: demo):
Source package (default: demo):


BUILD SUCCESSFUL
2 actionable tasks: 2 executed

project structure

├── gradle // Generated folder for wrapper files
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradlew  // Gradle wrapper start scripts
├── gradlew.bat 
├── settings.gradle // Settings file to define build name and subprojects
└── app
    ├── build.gradle // Build script of app project
    └── src
        ├── main
        │   └── java // Default Java source folder
        │       └── demo
        │           └── App.java
        └── test // Default Java test source folder
            └── java 
                └── demo
                    └── AppTest.java

Creating New Gradle Builds

Docker 中部署在线markdown编辑器 StackEdit

StackEdit是一个开放源码免费使用的 MarkDown 编辑器, 基于 PageDown,使用了 Stack Overflow 和其他 Stack Exchange 网站的 MarkDown 库,stackoverflow出品。

StackEdit是一个开放源码免费使用的 MarkDown 编辑器, 基于 PageDown,使用了 Stack Overflow 和其他 Stack Exchange 网站的 MarkDown 库,stackoverflow出品。

展示页面: https://stackedit.io/.

一、介绍

简介

这款在线markdown编辑器,是一款很赞的应用。如果你是markdown的用户,那么你要试一下这款应用;如果你是chrome用户,那么你更要安装这款应用。我是把它作为chrome的一个插件应用来使用的。下面来看一下这款应用的特点及使用技巧。

功能

  • 管理多个 MarkDown 文档,可在线或离线编辑
  • 通过模板导出 MarkDown 或 HTML 格式文件
  • 云同步 MarkDown 文档
  • 支持 Google Drive, Dropbox 和本地硬盘驱动器等备份
  • Post MarkDown 文档到 Blogger 、Blogspot、WordPress和Tumblr
  • 发布 MarkDown 文档在GitHub,GIST,Google Drive,Dropbox或任何SSH服务器
  • 分享一个及时渲染的 MarkDown 文档链接
  • 文档信息统计显示
  • 转换HTML到 MarkDown
  • 以GIst发布后支持分享(可以在线使用StackEdit阅读)

支持

  • 实时编辑、HTML预览并自动滚动定位到编辑处
  • Markdown Extra 支持 Prettify/Highlight.js 的语法高亮
  • LaTeX 数学表达式使用MathJax
  • 所见即所得的控制按钮
  • 可配置的布局
  • 支持多个主题
  • A la carte extensions
  • 离线编辑
  • Google Drive和Dropbox在线同步
  • 一键发布支持 Blogger,Dropbox,GIST,GitHub,Google Drive,SSH server,Tumblr,WordPress

Build Docker Image

FROM ubuntu:14.04

RUN apt-get update \
 && apt-get install -y curl git \
 && curl -sL https://deb.nodesource.com/setup_0.12 | bash - \
 && apt-get install -y nodejs \
 && rm -rf /var/lib/apt/lists/* \
 && git clone https://github.com/benweet/stackedit.git

WORKDIR /stackedit

RUN npm install \
 && npm install bower \
 && node_modules/bower/bin/bower install --production --config.interactive=false --allow-root

EXPOSE 3000

CMD nodejs server.js

build Image

docker build -t stackedit .

run container

docker run -d -p 3000:3000 stackedit

MongoDB 3.x 进行用户权限管理配置

关系型数据库都是有权限控制的,什么用户能访问什么库,什么表,什么用户可以插入,更新,而有的用户只有读取权限。

MongoDB常用命令

> show dbs  #显示数据库列表 
> show collections  #显示当前数据库中的集合(类似关系数据库中的表)
> show users  #显示用户
> use <db name>  #切换当前数据库,如果数据库不存在则创建数据库。 
> db.help()  #显示数据库操作命令,里面有很多的命令 
> db.foo.help()  #显示集合操作命令,同样有很多的命令,foo指的是当前数据库下,一个叫foo的集合,并非真正意义上的命令 
> db.foo.find()  #对于当前数据库中的foo集合进行数据查找(由于没有条件,会列出所有数据) 
> db.foo.find( { a : 1 } )  #对于当前数据库中的foo集合进行查找,条件是数据中有一个属性叫a,且a的值为1
> db.dropDatabase()  #删除当前使用数据库
> db.cloneDatabase("127.0.0.1")   #将指定机器上的数据库的数据克隆到当前数据库
> db.copyDatabase("mydb", "temp", "127.0.0.1")  #将本机的mydb的数据复制到temp数据库中
> db.repairDatabase()  #修复当前数据库
> db.getName()  #查看当前使用的数据库,也可以直接用db
> db.stats()  #显示当前db状态
> db.version()  #当前db版本
> db.getMongo()  #查看当前db的链接机器地址
> db.serverStatus()  #查看数据库服务器的状态

如果你想创建一个“myTest”的数据库,先运行use myTest命令,之后就做一些操作(如:db.createCollection('user')),这样就可以创建一个名叫“myTest”的数据库。

用户权限设置

  • MongoDB是没有默认管理员账号,所以要先添加管理员账号,再开启权限认证。
  • 切换到admin数据库,添加的账号才是管理员账号
  • 用户只能在用户所在数据库登录,包括管理员账号
  • 管理员可以管理所有数据库,但是不能直接管理其他数据库,要先在admin数据库认证后才可以。

添加管理员账号


> use admin 
> show collections
> db.createUser(
   {
     user: "admin",
     pwd: "password",
     roles: [ { role: "userAdminAnyDatabase", db: "admin" } ]
   }
)
Successfully added user: {
    "user" : "admin",
    "roles" : [
        {
            "role" : "userAdminAnyDatabase",
            "db" : "admin"
        }
    ]
}
> show users
> db.system.users.find()

开启权限验证

$ mongod --auth

验证权限是否生效

> show dbs
2016-07-19T17:32:37.258+0800 E QUERY    [thread1] Error: listDatabases failed:{
    "ok" : 0,
    "errmsg" : "not authorized on admin to execute command { listDatabases: 1.0 }",
    "code" : 13
} :
_getErrorWithCode@src/mongo/shell/utils.js:25:13
Mongo.prototype.getDBs@src/mongo/shell/mongo.js:62:1
shellHelper.show@src/mongo/shell/utils.js:760:19
shellHelper@src/mongo/shell/utils.js:650:15
@(shellhelp2):1:1
> use admin
switched to db admin
> db.auth('admin','mongodb:passok')
1
> show dbs
admin  0.000GB
local  0.000GB

添加普通用户

> use mydb
switched to db mydb
> db.createUser(
   {
     user: "tonny",
     pwd: "tonny@passok",
     roles: [ { role: "readWrite", db: "mydb" } ]
   }
)
Successfully added user: {
    "user" : "tonny",
    "roles" : [
        {
            "role" : "readWrite",
            "db" : "mydb"
        }
    ]
}
> exit
bye

MongoDB数据库角色

  • 内建的角色
  • 数据库用户角色:read、readWrite;
  • 数据库管理角色:dbAdmin、dbOwner、userAdmin;
  • 集群管理角色:clusterAdmin、clusterManager、clusterMonitor、hostManager;
  • 备份恢复角色:backup、restore;
  • 所有数据库角色:readAnyDatabase、readWriteAnyDatabase、userAdminAnyDatabase、dbAdminAnyDatabase
  • 超级用户角色:root // 这里还有几个角色间接或直接提供了系统超级用户的访问(dbOwner 、userAdmin、userAdminAnyDatabase)
  • 内部角色:__system

角色说明:
Read:允许用户读取指定数据库
readWrite:允许用户读写指定数据库
dbAdmin:允许用户在指定数据库中执行管理函数,如索引创建、删除,查看统计或访问system.profile
userAdmin:允许用户向system.users集合写入,可以找指定数据库里创建、删除和管理用户
clusterAdmin:只在admin数据库中可用,赋予用户所有分片和复制集相关函数的管理权限。
readAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的读权限
readWriteAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的读写权限
userAdminAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的userAdmin权限
dbAdminAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的dbAdmin权限。
root:只在admin数据库中可用。超级账号,超级权限

swift4 语法备忘 - Methods

Methods

Methods are functions that are associated with a particular type

  • Instance methods, which encapsulate specific tasks and functionality for working with an instance of a given type.
  • Type methods, which are associated with the type itself.

Instance Methods

Instance methods are functions that belong to instances of a particular class, structure, or enumeration. They support the functionality of those instances, either by providing ways to access and modify instance properties, or by providing functionality related to the instance’s purpose.

An instance method has implicit access to all other instance methods and properties of that type

class Counter {
    var count = 0
    func increment() {
        count += 1
    }
    func increment(by amount: Int) {
        count += amount
    }
    func reset() {
        count = 0
    }
}

let counter = Counter()
// the initial counter value is 0
counter.increment()
// the counter's value is now 1
counter.increment(by: 5)
// the counter's value is now 6
counter.reset()
// the counter's value is now 0

The self Property

Every instance of a type has an implicit property called self, which is exactly equivalent to the instance itself. You use the self property to refer to the current instance within its own instance methods.

// 
struct Point {
    var x = 0.0, y = 0.0
    func isToTheRightOf(x: Double) -> Bool {
       // You use the self distinguish between the parameter name and the property name.
        return self.x > x
    }
}

Modifying Value Types from Within Instance Methods

Structures and enumerations are value types. By default, the properties of a value type cannot be modified from within its instance methods.

if you need to modify the properties of your structure or enumeration within a particular method, You can opt in to this behavior by placing the mutating keyword before the func keyword for that method

struct Point {
    var x = 0.0, y = 0.0
    mutating func moveBy(x deltaX: Double, y deltaY: Double) {
        x += deltaX
        y += deltaY
    }
}
var somePoint = Point(x: 1.0, y: 1.0)
somePoint.moveBy(x: 2.0, y: 3.0)
print("The point is now at (\(somePoint.x), \(somePoint.y))")

Assigning to self Within a Mutating Method

Mutating methods can assign an entirely new instance to the implicit self property.

struct Point {
    var x = 0.0, y = 0.0
    mutating func moveBy(x deltaX: Double, y deltaY: Double) {
        self = Point(x: x + deltaX, y: y + deltaY)
    }
}

enum TriStateSwitch {
    case off, low, high
    mutating func next() {
        switch self {
        case .off:
            self = .low
        case .low:
            self = .high
        case .high:
            self = .off
        }
    }
}
var ovenLight = TriStateSwitch.low
ovenLight.next()
// ovenLight is now equal to .high
ovenLight.next()
// ovenLight is now equal to .off

Type Methods

Methods that are called on the type itself. These kinds of methods are called type methods. You indicate type methods by writing the static keyword before the method’s func keyword.

Type methods are called with dot syntax, like instance methods. However, you call type methods on the type,

class SomeClass {
    class func someTypeMethod() {
        // type method implementation goes here
    }
}
SomeClass.someTypeMethod()

D3 相关知识点归纳

var theData = [ 1, 2, 3 ]
var p = d3.select("body").selectAll("p")
 .data(theData)
  .enter()
  .append("p")
  .text("hello ");

会生成以下的结构

<body>
  <p>hello </p>
  <p>hello </p>
  <p>hello </p>
</body>

selectAll 方法使用类似css选择器的语法去抓取DOM元素,
不像select方法那样(只返回第一个选中的元素),
它将选中所有match中的elements,
但是当前的HTML 文档流上面根本不存在<p>元素啊,它究竟做了什么鬼,
其实它选中的是所有即将可用的<p>而已,在此时,p是不存在的,
它返回了一个空的selection

再后面的用到了.data(theData)enter()后 ,我们就可以把数据和空的selection进行绑定

data操作符返回了三个 virtual selections

这三个分别是

  • enter
  • update
  • exit

enter selection它包含了一些确是元素的placeholder
update selection 它包含了一些存在的元素和data进行数据的绑定
exit selection 它包含剩下被删除的元素

Telegram 下通过ehForwarderBot收发微信

本文主要记录通过 隧道机器人框架 EH Forwarder Bot

实现了通过 Telegram Bot收发微信功能,无论文字、语音、图片还是视频、表情,都可以互通.

Docker 下部署 EFB 框架

EFB 的搭建有一些门槛,主要的问题是 Python 3 的配置问题,新手难免碰了不少坑。
但用上Docker环境部署,EFB 的搭建过程就非常方便。

  1. 环境要求

系统为 Ubuntu 16.04 x64

Welcome to Ubuntu 16.04.2 LTS (GNU/Linux 4.12.9-041209-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

Welcome to Alibaba Cloud Elastic Compute Service !
  1. 安装 docker-ce

具体官方教程 Ubuntu 下安装docker

sudo apt-get -y install apt-transport-https ca-certificates curl
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
sudo apt-get update
sudo apt-get -y install docker-ce
  1. 安装EFB对应打包好的镜像

镜像地址 docker-EFB

docker pull royx/docker-efb
  1. 配置 Telegram Bot 机器人

EFB 目前实现的 Telegram 与 微信 互通的流程:

Telegram bot > EFB > 微信网页版 > 微信

先配置 Telegram bot

  • 在 Telegram 里搜索 botfather 并和他对话
  • @botfather 说话,输入 /newbot
  • 给你的机器人 bot 起个名字
  • 继续给机器人起用户名,和上面的名字可以相同,但必须以 bot 结尾
  • 获得机器人 Token

设置 bot 隐私权限

继续与 @botfather 对话,输入 /setprivacy,选择刚刚创建的机器人,点击 Disable

获得 Telegram ID

再和另外一个机器人 @get_id_bot 对话,点击 start 即可获得你的 Telegram ID,一串数字(Chat ID)。

至此,Telegram 的配置完成,我们得到两个重要的数字:tokenTelegram ID

  1. 配置 EFB

新建一份配置文件 vim config.py

master_channel = 'plugins.eh_telegram_master', 'TelegramChannel'
slave_channels = [('plugins.eh_wechat_slave', 'WeChatChannel')]
 
eh_telegram_master = {
    "token": "12345678:QWFPGJLUYarstdheioZXCVBKM",
    "admins": [13456782],
    "bing_speech_api": ["xxx", "xxx"],
    "baidu_speech_api": {
        "app_id": 0,
        "api_key": "xxx",
        "secret_key": "xxx"
    }
}

注意把上面 token 以及 admins 冒号后面的部分替换成刚刚获得的 tokenChat ID

新建一个 tgdata.db 文件

touch tgdata.db

简单启动容器

$ docker run -d --restart=always \
        --name=ehforwarderbot \
        -v $(pwd)/config.py:/opt/ehForwarderBot/config.py \
        -v $(pwd)/tgdata.db:/opt/ehForwarderBot/plugins/eh_telegram_master/tgdata.db \
        royx/docker-efb

另外推荐用docker-compose 启动和管理容器

sudo curl -L https://github.com/docker/compose/releases/download/1.19.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose

sudo chmod +x /usr/local/bin/docker-compose

创建docker-compoes.yml 文件, config.py 以及 tgdata.db 放在同一个文件夹

ehforwarderbot:
  image: royx/docker-efb:v1.6.4
  container_name: ehforwarderbot
  volumes:
    - ./config.py:/opt/ehForwarderBot/config.py
    - ./tgdata.db:/opt/ehForwarderBot/plugins/eh_telegram_master/tgdata.db

在该目录下执行 docker-compose up -d

通过 docker-compose logs -f 可以查看日志里输出的二维码之类的信息(CTRL + C 退出)

更新的话修改docker-compose.yml文件之后
重新执行 docker-compose up -d 即可

登录微信

现在,我们只需要登录微信,就可以了:

 docker logs ehforwarderbot

在屏幕上,你会看到一个二维码,用微信扫描,登录即可。

Ubuntu下 搭建ehForwarderBot文档

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.