在使用 JS 的项目中使用 ESLint 强制进行 code style check

code style 是一个经常被大家提起的东西. 当项目中人少的时候, 大家一般都能有一个约定, 并去遵守.

但是当项目变大, 或者有新的开发人员加入进来的时候, code style 不一致的问题就会变大. 甚至有些写法不规范的地方, 导致看代码的时候感觉不舒服. 假如每个人喜欢的风格不一致, 当编辑代码的时候, 又使用了代码自动格式化工具按照自己的喜好进行格式化, 那么当提交到 git 的时候, 会出现全红全绿的情况, 特别不利于 review.

因此应该采取一种手段进行约束.

所以我采用了 git hook 的方式在本地强制进行 code style check. 当 check fail 时, 直接拒绝 commit. 防止出现为了修复 code style 而添加的一些意义不大的 commit.

  1. 项目安装 ESLint, 并配置 eslintrceslintignore
  2. 在项目的 .git-hooks 目录下添加如下脚本, 来源 https://gist.github.com/linhmtran168/2286aeafe747e78f53bf

    #!/bin/sh
    
    STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep ".jsx\{0,1\}$")
    
    if [[ "$STAGED_FILES" = "" ]]; then
      exit 0
    fi
    
    # Check for eslint
    ESLINT="./node_modules/.bin/eslint"
    if [[ ! -e $ESLINT ]]; then
      echo ""
      echo "  \033[41mPlease use <npm install> to install ESlint\033[0m"
      exit 1
    fi;
    
    PASS=true
    echo ""
    
    for FILE in $STAGED_FILES
    do
      $ESLINT "$FILE"
    
      if [[ "$?" == 0 ]]; then
        echo "  \033[32mESLint Passed: $FILE\033[0m"
      else
        echo "  \033[41mESLint Failed: $FILE\033[0m"
        PASS=false
      fi
    done
    
    if ! $PASS; then
      echo "\n\033[41mCOMMIT FAILED:\033[0m Your commit contains files that should pass ESLint but do not. Please fix the ESLint errors and try again.\n"
      exit 1
    else
      echo "\n\033[42mCOMMIT SUCCEEDED\033[0m\n"
    fi
    
    exit $?
    
  3. package.json 中进行配置 postinstall 为如下内容

    cp .git-hooks/* .git/hooks/
    

这样, 每次 npm install 之后, 都会将检查脚本放置到正确的位置. 每次 git commit 的时候, 都会进行 code style check.

我们目前的项目中, 使用了 FIS3 进行开发, 所以我们开发时, 会有对应的 npm run dev 这样的 script, 为了确保检查脚本能被正确放置, 我在 npm run dev 中也会去跑一遍 npm run postinstall. 这样, 就不会出现, 由于原来已经 npm install 过了, 而没有把检查脚本正确放置的情况.

webpack 的 chunkId 是不是一个好的概念

webpack 因为有各种各样的 loader, 可以让我们把所有的东西都使用相同的 require 写法进行加载, 同时还可以配合各种 compiler, 让我们从现在就可以享受到 ES6 给我们带来的便利, 所以 webpack 受到了大家的欢迎, 成为 2015 - 2016 特别受欢迎的打包及模块化方案.

但是在使用 webpack 的时候, 遇到了一个不小的问题, webpack 的 chunkId 对于永久缓存的支持.

假设我们有这样一个 SPA 页面场景, 有一个路由文件 route.js, 会在此文件中使用 webpack 的 code spliting 写法, 按需加载文件.

在 route.js 中分别对应三个页面(foo, bar, baz), 按需加载对应的入口文件. 在入口文件中, 会再分别加载各个子页面依赖的文件.

当我们使用 webpack 进行 build 后, webpack 会给每个文件分配一个 chunkId, 这个 chunkId 是随着 webpack 处理到的文件的数目而进行递增的. 一种结果是类似于下面这样的, 文件后面的括号中是生成的 chunkId 号

如果我们需要在foo.js中增加一个依赖 dep4.js, 那么相应的 chunkId 会变成类似于下面这样

我们只增加(或删除)了一个文件, 却导致了多个文件的 chunkId 变化, 这样就导致多个文件的内容发生了变化. 那么当重新发布后, 其实有的页面的内容根本不需要变化, 但是仅仅是因为 chunkId 变化, 而导致需要重新下载这些文件, 使得没法使用浏览器已经缓存的文件.

如果你的页面有几十个, 每次添加或者删除一个文件后, 都会导致几乎所有的文件的浏览器缓存失效.

我们期望的结果是, 每当我们修改一个文件后, 只有依赖此文件的文件会更新, 而其它的文件不会发生变化. 当重新发布后, 只会去重新下载那些已经被更新的文件, 这样我们就可以做到类似于增量更新的效果.

但是 webpack 采用的 chunkId 的概念好像没办法避免这样的问题. 如果将 chunkId 换成依赖文件的路径, 是不是可以减少这种问题的发生呢?

使用 System.js 作为 ES6 的加载器

随着 ES6 的慢慢普及以及各浏览器对于 ES6 的支持, 在新开的项目或者对于老项目的更新上, 大家纷纷采取拥抱 ES6 的态度. 因为即使有的浏览器目前对于 ES6 的支持还不太高, 但是我们可以使用 webpack, 加上 webpack 中的各种 loader, 我们可以很方便的将 ES6 编译为普通的 ES5 代码, 然后跑在所有的浏览器上.

webpack 为大家提供了 webpack-dev-server 用于开发环境, webpack-dev-server 可以方便的进行代码调试, reload 等功能.

但是我在使用 webpack 搭配 webpack-dev-server 的时候, 由于项目很大, 文件特别多, 所以遇到了一个很大的问题, rebuild 等待的时间太久了, 完全不如我原来没有使用 webpack 时保存代码后手动刷新浏览器来的快速. 但是因为我使用了 webpack 进行合并压缩, 我不得不等待整个项目编译完成才能进行调试.

为了解决这个问题, 我写了 webpack-source-code-loader, 并且写了一篇文章 在Angular.js项目中使用异步加载(五).

后来接触到了 System.js, 发现其实我的需求完全可以使用 System.js 进行解决. 因为 System.js 可以通过 Babel.js 或者 TypeScript 在浏览器中完成 ES6 => ES5 的编译工作. 具体例子请看我写的 demo

在使用的时候我发现, 使用 TypeScript 时会比使用 Babel.js 有更快的编译速度, 同时还可以使用 TypeScript 提供的类型系统, 真的很好. 并且, System.js 和 Babel.js@6.x 有兼容性问题, 目前还只能使用 Babel.js@5.x 才能进行正常工作.

综上, 以后开始一个新的项目的时候, 完全可以先不管 webpack 的配置, 可以直接使用 System.js 来加载 ES6 源码, 等到整个项目完成的差不多了, 再考虑使用 webpack 或者其它构建工具进行工程化也不迟.

在Angular.js项目中使用异步加载(六) - 动态加载第三方模块

在 Anguarl.js 中使用异步加载, 并配合构建工具, 基本已经可以在生产环境中进行使用. 但是还有一些情况并没有覆盖到.

比如, 整个项目的某个页面需要依赖第三方模块, 但是以 Angular.js 加载模块的方式, 就需要在页面初始化之前把第三方模块加载完毕, 同时在项目初始化的依赖部分加以引用, 打包资源时, 就需要把这个依赖同样引入. 这样就会造成加载资源的浪费.

由于目前我所在的项目中, 基本都是些全局依赖, 所以没有遇到这种需求. 也是这个原因, 导致我对这个方向没有做过研究. 但是没需求不代表没问题, 如果某天我也遇到了这样的问题, 可能就要费一番功夫了.

正好在SegmentFault上看到一篇文章, 讲述怎么动态加载第三方模块, 并在项目中进行了试验, 可以解决这个问题.

文章地址 angularjs+requirejs实现按需加载的全面实践, 还有一篇英文的 AngularJS: RequireJS, dynamic loading and pluggable views, 这篇中我只看了其中动态加载模块的部分.

解决方法, 就是按照正常加载模块的流程, 将 Angular.js 内部的逻辑走一遍.

一个模块在定义时, 会有好多方法, 比如 controller, directive, filter, factory, service, provider, value, constant, decorator. 但是调用这些方法的最终结果, 是把那些具体的逻辑放入三个数组中, 这三个数组为

等到需要使用这个模块的时候, 再进行执行. 在 Angular.js 中, 如果不在项目的定义时引入依赖, 即使加载了第三方模块也不能用的原因就是因为, 第三方模块只进行了定义, 而那些具体的逻辑并没有得到执行, 所以才会报各种 *Provider 找不到的错误.

动态加载第三方模块的关键就是将第三方模块的初始化逻辑进行执行. 所以我们将 app.js 中的 config 部分进行改写.

app.config([
  '$controllerProvider',
  '$compileProvider',
  '$filterProvider',
  '$provide',
  '$injector',
  function($controllerProvider, $compileProvider, $filterProvider, $provide, $injector) {
    app.controller = $controllerProvider.register;
    app.directive = $compileProvider.directive;
    app.filter = $filterProvider.register;
    app.factory = $provide.factory;
    app.service = $provide.service;
    app.provider = $provide.provider;
    app.value = $provide.value;
    app.constant = $provide.constant;
    app.decorator = $provide.decorator;

    // 在这里定义一个新的全局方法, 方便进行第三方模块的加载
    window.Library = (function(){

      var providers = {
        '$controllerProvider': $controllerProvider,
        '$compileProvider': $compileProvider,
        '$filterProvider': $filterProvider,
        '$provide': $provide
      };

      var cache = {};

      return function Library(module){

        // already activated
        if(cache[module]){
          return;
        }

        var module = angular.module(module);

        var i;

        if (module.requires) {
          for (i = 0; i < module.requires.length; i++) {
            Library(module.requires[i]);
          }
        }

        var invokeArgs, provider, method, args;
        for(i = 0; i < module._invokeQueue.length; i++){
          invokeArgs = module._invokeQueue[i];
          provider = providers[invokeArgs[0]];
          method = invokeArgs[1];
          args = invokeArgs[2];
          provider[method].apply(provider, args);
        }

        for(i = 0; i < module._configBlocks.length; i++){
          $injector.invoke(module._configBlocks[i]);
        }

        for(i = 0; i < module._runBlocks.length; i++){
          $injector.invoke(module._runBlocks[i]);
        }

        cache[module] = true;
      };
    })();
  }
]);

使用方法很简单, 假如有一个第三方模块按照正常的模块定义方式进行书写. 我们可以有一个只进行引入第三方模块的文件. 在这个模块中, 进行第三方模块的载入工作.

define([
  'path/to/library.js'
], Ready(function(){
  Library('libraryName');
}));

当我们在其它文件需要加载此第三方模块时, 不去依赖原文件, 而是依赖我们写的文件即可

近期文章

相关页面