NO END FOR LEARNING

Writing blog if you feel tired | 学海无涯 苦写博客

用Webpack构建和打包模块(JS,SCSS,HTML等)基础(译)

| Comments

本文翻译自 http://blog.madewithlove.be/post/webpack-your-bags/ 的部分内容

译者:原文作者很幽默写了许多幽默的语句,但是为了节省时间,我就不翻译那些营养不多的句子,捡核心有用的内容翻译。

什么是webpack?

Webpack是构建系统还是模块bundler。Well,两者都是,但不是说它两件事情都做,而是将两件事情合并在一起,一起做。Webpack没有构建你的assets,然后在单独打包你的模块,而是把assets也当做模块。

更准确的说,它没有构建Sass文件,优化图片,并把它们放在一边后,再打包你的模块,然后把模块放在另一边,相反,你将拥有这些:

1
2
3
4
5
6
7
import stylesheet from 'styles/my-styles.scss';
import logo from 'img/my-logo.svg';
import someTemplate from 'html/some-template.html';

console.log(stylesheet); // "body{font-size:12px}"
console.log(logo); // "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5[...]"
console.log(someTemplate) // "<html><body><h1>Hello</h1></body></html>"

所有的你的assets都会被当做是一种模块,然后被导入,修改,操作,并最终打包到最后的bundle文件中。

当然为了实现上面说的这些事情,需要在Webpack的配置中注册loader。Loader是一些小的插件,基本上就是说“当你遇到某种类型的文件时,做这个操作”。比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
  // When you import a .ts file, parse it with Typescript
  test: /\.ts/,
  loader: 'typescript',
},
{
  // When you encounter images, compress them with image-webpack (wrapper around imagemin)
  // and then inline them as data64 URLs
  test: /\.(png|jpg|svg)/,
  loaders: ['url', 'image-webpack'],
},
{
  // When you encounter SCSS files, parse them with node-sass, then pass autoprefixer on them
  // then return the results as a string of CSS
  test: /\.scss/,
  loaders: ['css', 'autoprefixer', 'sass'],
}

最终在食物链的底层,所有的loader都会返回String。这样Webpack会把他们wrap到JavaScript模块中。比如每个Sass文件在被loader给transform之后,它可能看上去像这样:

1
export default 'body{font-size:12px}';

为什么要这样做?

一旦你知道了Webpack做了什么,第二问题一定是:这样做有什么好处?“Images and CSS? In my JS? What the hell man?”。Well,这样想:长期以来,我们都被告知要将所有的东西都集中到一起,减少我们的HTTP请求。

这样也导致一个不好的情况,今天大部分人都讲所有的assets打包到一个app.js文件中,然后在所有的页面都加载它。这意味着对于任何一个页面在大部分时间都在加载一大堆根本不需要的assets。如果你不这样做,那么你就需要手动的在某个页面加入这些东西,这样会导致要维护一大堆依赖,哪个页面需要这些依赖?

上面两个方法没有一个是正确的或者错误的。Webpack采取了一种折中方式(middleground) - 它不仅仅是构建系统或者打包工具,他是一个顽皮聪明的模块打包系统。一旦被合理配置,他会比你更了解你的技术栈,比你更清楚如何最优化它们。

简单试用

我们来创建一个项目,并用Webpack构建/打包它。

1
2
3
$ npm init
$ npm install jquery --save
$ npm install webpack --save-dev

src/index.js

1
2
var $ = require('jquery');
$('body').html('Hello');

创建一个webpack.config.js文件。Webpack的配置文件就是JavaScript,但是需要export这个对象。

webpack.config.js

1
2
3
4
5
6
7
module.exports = {
    entry:  './src',
    output: {
        path:     'builds',
        filename: 'bundle.js',
    },
};

这里,entry告诉Webpack哪些文件作为应用程序的入口。它们是你的主要文件,是坐落在项目依赖的最顶部。然后,我们告诉它编译,并输出到build目录下的bundle.js中。接着,在index.html中配置引入bundle.js文件。

index.html

1
2
3
4
5
6
7
8
9
<!DOCTYPE html>
<html>
<body>
    <h1>My title</h1>
    <a>Click me</a>

    <script src="builds/bundle.js"></script>
</body>
</html>

运行webpack命令,如果一切顺利,你应该看到下面这个信息:

1
2
3
4
5
6
7
8
$ webpack
Hash: 65ea4aab7f0755f7fc8e
Version: webpack 1.12.2
Time: 350ms
    Asset    Size  Chunks             Chunk Names
bundle.js  257 kB       0  [emitted]  main
   [0] ./src/index.js 53 bytes {0} [built]
    + 1 hidden modules

这里你看到Webpack告诉你在bundle.js文件中包含入口文件(index.js),已经一个隐藏的模块,jQuery。默认情况下,Webpack会隐藏不属于你的模块。如果你想显示它们,使用–display-modules标识符。

1
2
3
4
$ webpack --display-modules
bundle.js  257 kB       0  [emitted]  main
   [0] ./src/index.js 53 bytes {0} [built]
   [1] ./~/jquery/dist/jquery.js 248 kB {0} [built]

你也可以运行webpack –watch,他会自动watch你的文件,并在有需要时重新编译。

配置你的第一个loader

还记得我们谈到过Webpack可以导入css和html等所有的东西?怎么做到的呢?Well,如果你和最近几年Web Component的步调一致(Angular 2, Vue, React, Polymer, X-Tag, etc.)。也许你已经听说过这个idea,与相互链接庞大的UI不同,Web Component是一系列可维护和自包含的,小的,可重用的UI片段(piece)。为了让组件可以真正的自包含,需要将所有它所需要的东西都打包进去。想象一个按钮组件:有一些HTML代码,需要一些JS代码来让组件可以交互,也许还需要一个样式。如果这些东西只有在需要的时候才被加载,就更好了。

让我们来写一个Button。首先,我假设大部分人现在都更习惯与用ES2015语法,我们来添加第一个loader:Babel。在Webpack中添加添加一个loader,你需要做两件事情:npm install {whatever}-loader,然后在Webpack的配置的module.loaders部分添加该loader。所以:

1
$ npm install babel-loader --save-dev

更新我们的配置:我们想要什么?想要Babel运行所有的以.js结尾的文件,但是Webpack会递归遍历所有的依赖,我们需要Babel避免运行在第三方代码中,比如jQuery,所以需要过滤一下。loader可以有include和exclude规则,可以是字符串,正则,回调等等。在我们的例子中,我们希望Babel仅仅在我们自己的文件上运行,所以我们只需要include我们的源文件目录即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
module.exports = {
    entry:  './src',
    output: {
        path:     'builds',
        filename: 'bundle.js',
    },
    module: {
        loaders: [
            {
                test:   /\\.js/,
                loader: 'babel',
                include: \_\_dirname + '/src',
            }
        ],
    }
};

现在来重写index.js,用ES6的语法。

1
2
3
import $ from 'jquery';

$('body').html('Hello');

第一个自包含的组件

现在来写一个小的Button组件,它会包含SCSS样式,HTML模板,和一些行为,所以我们会需要安装一些需要的东西。首先,我们需要Mustache,它是一个轻量级的模板引擎(译者:如果你不知道JavaScript模板引擎,可以查看这篇文章 http://benweizhu.github.io/blog/2015/10/28/js-template-engine/ ),这时我们也需要Sass和HTML的loader。而且,最终需要load的内容会从一个loader流向另一个loader,我们需要一个CSS loader来吹了Sass loader的处理结果,现在一旦我们有了自己的CSS,就有很多方法来处理它们,现在我们会使用一个叫做style-loader的loader,它会取出CSS片段,并把它动态的插入页面。

1
2
$ npm install mustache --save
$ npm install css-loader style-loader html-loader sass-loader node-sass --save-dev

现在有序的告诉Webpack让load的内容从一个loader流入另一个,我们简单的传入一系列loader,从右到左,用一个!(感叹号)分开。或者你也可以通过一个数组,并使用loaders属性而不是loader属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
    test:    /\.js/,
    loader:  'babel',
    include: \_\_dirname + '/src',
},
{
    test:   /\\.scss/,
    loader: 'style!css!sass',
    // Or
    loaders: ['style', 'css', 'sass'],
},
{
    test:   /\\.html/,
    loader: 'html',
}

现在来写我们的Button组件:

src/Components/Button.scss

1
2
3
4
.button {
  background: tomato;
  color: white;
}

src/Components/Button.html

1
<a class="button" href=""></a>

src/Components/Button.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import $ from 'jquery';
import template from './Button.html';
import Mustache from 'mustache';
import './Button.scss';

export default class Button {
    constructor(link) {
        this.link = link;
    }

    onClick(event) {
        event.preventDefault();
        alert(this.link);
    }

    render(node) {
        const text = $(node).text();

        // Render our button
        $(node).html(
            Mustache.render(template, {text})
        );

        // Attach our listeners
        $('.button').click(this.onClick.bind(this));
    }
}

无论何时被导入,在哪个上下文中运行,Button.js是100%自包含的,它会包含所有需要的工具,并正确的渲染,现在我们只需要直接在页面上渲染我们的Button即可。

1
2
3
4
import Button from './Components/Button';

const button = new Button('google.com');
button.render('a');

参考:http://blog.madewithlove.be/post/webpack-your-bags/

了解Spring Cache

| Comments

自从Spring 3.1,Spring框架提供了透明式给Spring应用添加缓存。类似于对事物的支持,抽象缓存机制允许一致的方式来实现不同的缓存解决方案,以满足对代码的最小影响。

Spring 4.1后,抽象缓存机制有了重大的改进,包含支持JSR-107注解和更多的定制选项。

Cache vs Buffer

术语“buffer”和“cache”倾向于交换的使用。然而,它们代表着不同的东西。buffer常常被作为快慢实体之间(比如:发送方很快,接收方较慢)的中间临时存储。乙方必须等待另一方,因为执行效率(速率)的不同,buffer通过允许数据在移动时,以一整块数据为单位而不是小块形式来调解这种速率不一致的问题。数据在buffer中只会读写一次。更进一步,至少对于一方来说buffer是可见的。

另一方面,Cache就定义上而言,对于任何一方都是隐藏。它也会提升性能,但是仅限于同样地数据以快速的方式读取多次。

Cache的核心是将抽象层应用到Java方法上,减少方法执行的次数,因为信息在Cache中可以获取到。那就是,每次目标方法被触发时,抽象层就会执行一个cache行为,来检测方法是否已经针对指定的参数执行过一次。如果是,那么就会cache结果,而不会执行实际方法;如果没有,那么就执行相应的方法,方法执行的结果会被cache,下次同样的方法和参数执行的时候,就会返回cache的结果。这样,对于消耗更大的方法(无论是CPU还是IO)都只会针对给定的参数执行一次。Cache的逻辑完全是透明的,不会对触发调用产生任何干扰。

该抽象层也提供了其他Cache相关的操作的,允许更新Cache的内容或者删除一部分内容(条目)。这对于需要在应用程序运行阶段改变Cache的情况非常有用。

就像其他Spring框架的其他服务,Cache服务只是一个抽象层,需要实际的存储方式来存储数据。抽象层的关键类是org.springframework.cache.Cache和org.springframework.cache.CacheManager接口。

有几种开箱即用的实现:JDK java.util.concurrent.ConcurrentMap based caches, EhCache, Gemfire cache, Guava caches, JSR-107 compliant caches。

要使用cache抽象机制,需要负责两个方面: cache声明 - 指定要被cache的方法以及策略 cache配置 - 背后的cache存储在哪里,从哪里读取

关键注解

1
2
3
4
5
@Cacheable triggers cache population
@CacheEvict triggers cache eviction
@CachePut updates the cache without interfering with the method execution
@Caching regroups multiple cache operations to be applied on a method
@CacheConfig shares some common cache-related settings at class-level

@Cacheable用来声明方法可以被缓存(cache)

1
2
@Cacheable("books")
public Book findBook(ISBN isbn) {...}

其中,books是该cache的名字。
在大部分情况下,只有一个cache被声明,注解允许多个名字被指定,所以就可以有多个cache被使用。在这种情况下,每个cache都会在方法执行之前被检测,如果至少有一个cache命中,那么相关的值就会被返回:

1
2
@Cacheable({"books", "isbns"})
public Book findBook(ISBN isbn) {...}

cache本质上是键值对存储,每一个cache的方法在被触发时,都会转换成一个合适的key来访问cache。开箱即用的是,有一个简单地KeyGenerator基于下面这个简单算法:

如果没有参数,就返回SimpleKey.EMPTY
如果有一个参数,就返回该实例
如果有多个参数,就返回一个SimpleKey包含所有的参数

只要参数包含自然键值,并且实现了合法的hashCode和equals方法,这种算法就适用于所有场景。

默认情况下,Cache抽象层会使用一个简单地CacheResolver来获取cache。

有条件的cache

有些时候,一个方法并不适合在任何时候都cache,cache注解支持通过SpEL来指定条件,只有满足条件的参数来会被cache。

1
2
@Cacheable(cacheNames="book", condition="#name.length < 32")
public Book findBook(String name)

cache抽象不仅允许存储缓存,也允许收回(eviction)缓存。这个操作对于删除陈旧无用的缓存非常有用。和注解@Cacheable相反,@CacheEvict指定方法执行cache收回,也就是方法执行时会从cache中删除数据。和@Cacheable需要指定一个或者多个cache的名字。还提供一个额外allEntries属性,用来指定是否需要一个更广范围的cache需要收回(所有条目(无参数的,一个参数的,多个参数的))。

1
2
@CacheEvict(cacheNames="books", allEntries=true)
public void loadBooks(InputStream batch)

CacheConfig

如果一个类中的所有方法操作都需要Cache,那么就可以使用@CacheConfig注解。

1
2
3
4
5
@CacheConfig("books")
public class BookRepositoryImpl implements BookRepository {
    @Cacheable
    public Book findBook(ISBN isbn) {...}
}

EnableCaching

仅仅声明上面你需要cache的方法或者类是不足够的,和Spring的其他服务一样,该特性需要被启动(Enable)。

1
2
3
4
@Configuration
@EnableCaching
public class AppConfig {
}

Cache正如它在对于Buffer时所解释的那样,用来解决对重复获取的数据,减少对数据库的重复操作,从而提高数据获取速度。

参考资料:

1.http://docs.spring.io/spring/docs/current/spring-framework-reference/html/cache.html