xml地图|网站地图|网站标签 [设为首页] [加入收藏]
配置最佳实践
分类:web前端

详解CSS display:inline-block的应用

2015/11/03 · CSS · display

原文出处: 小灰狼的脑瓜   

本文详细描述了display:inline-block的基础知识,产生的问题和解决方法以及其常见的应用场景,加深了对inline-block应用的进一步理解。

Webpack 4 配置最佳实践

2018/06/22 · JavaScript · webpack

原文出处: Zindex   

Webpack 4 发布已经有一段时间了。Webpack 的版本号已经来到了 4.12.x。但因为 Webpack 官方还没有完成迁移指南,在文档层面上还有所欠缺,大部分人对升级 Webpack 还是一头雾水。

不过 Webpack 的开发团队已经写了一些零散的文章,官网上也有了新版配置的文档。社区中一些开发者也已经成功试水,升级到了 Webpack 4,并且总结成了博客。所以我也终于去了解了 Webpack 4 的具体情况。以下就是我对迁移到 Webpack 4 的一些经验。

本文的重点在:

  • Webpack 4 在配置上带来了哪些便利?要迁移需要修改配置文件的哪些内容?
  • 之前的 Webpack 配置最佳实践在 Webpack 4 这个版本,还适用吗?

虚拟 DOM 已死?

2016/10/24 · 基础技术 · 1 评论 · DOM

本文作者: 伯乐在线 - ThoughtWorks 。未经作者许可,禁止转载!
欢迎加入伯乐在线 专栏作者。

本系列文章:

  • 《为什么 ReactJS 不适合复杂的前端项目?》
  • 《React.Component 损害了复用性?》

本系列的上一篇文章《React.Component 损害了复用性?》探讨了如何在前端开发中编写可复用的界面元素。本篇文章将从性能和算法的角度比较 Binding.scala 和其他框架的渲染机制。

Binding.scala 实现了一套精确数据绑定机制,通过在模板中使用 bindfor/yield 来渲染页面。你可能用过一些其他 Web 框架,大多使用脏检查或者虚拟 DOM 机制。和它们相比,Binding.scala 的精确数据绑定机制使用更简单、代码更健壮、性能更高。

基础知识

display:inline-block是什么呢?相信大家对这个属性并不陌生,根据名字inline-block我们就可以大概猜出它是结合了inline和block两者的特性于一身,简单的说:设置了inline-block属性的元素既拥有了block元素可以设置width和height的特性,又保持了inline元素不换行的特性。

举例说明:以前我们做横向菜单列表的时候,我们可以通过li和float:left两者来实现,现在可以通过li和display:inline-block。

HTML代码:

XHTML

<ul> <li>首页</li> <li>关于</li> <li>热点</li> <li>联系我们</li> </ul>

1
2
3
4
5
6
<ul>
    <li>首页</li>
    <li>关于</li>
    <li>热点</li>
    <li>联系我们</li>
</ul>

CSS代码

CSS

ul, li { padding: 0; margin: 0; list-style-type: none; } li { display: inline-block; border: 1px solid #000; }

1
2
3
4
5
6
7
8
9
ul, li {
    padding: 0;
    margin: 0;
    list-style-type: none;
}
li {
    display: inline-block;
    border: 1px solid #000;
}

效果图

图片 1

inline-block基本效果

可以看到li元素可以横向排列,并且可以设置width属性,大家可以复制代码自己查看效果或查看Demo

Webpack 4 之前的 Webpack 最佳实践

这里以 Vue 官方的 Webpack 模板 vuejs-templates/webpack 为例,说说 Webpack 4 之前,社区里比较成熟的 Webpack 配置文件是怎样组织的。

ReactJS虚拟DOM的缺点

比如, ReactJS 使用虚拟 DOM 机制,让前端开发者为每个组件提供一个 render 函数。render 函数把 propsstate 转换成 ReactJS 的虚拟 DOM,然后 ReactJS 框架根据render 返回的虚拟 DOM 创建相同结构的真实 DOM。

每当 state 更改时,ReactJS 框架重新调用 render 函数,获取新的虚拟 DOM 。然后,框架会比较上次生成的虚拟 DOM 和新的虚拟 DOM 有哪些差异,进而把差异应用到真实 DOM 上。

这样做有两大缺点:

  1. 每次 state 更改,render 函数都要生成完整的虚拟 DOM,哪怕 state 改动很小,render函数也会完整计算一遍。如果 render 函数很复杂,这个过程就会白白浪费很多计算资源。
  2. ReactJS 框架比较虚拟 DOM 差异的过程,既慢又容易出错。比如,你想要在某个 <ul> 列表的顶部插入一项 <li> ,那么 ReactJS 框架会误以为你修改了 <ul> 的每一项 <li>,然后在尾部插入了一个 <li>

这是因为 ReactJS 收到的新旧两个虚拟 DOM 之间相互独立,ReactJS 并不知道数据源发生了什么操作,只能根据新旧两个虚拟 DOM 来猜测需要执行的操作。自动的猜测算法既不准又慢,必须要前端开发者手动提供 key 属性、shouldComponentUpdate 方法、componentDidUpdate 方法或者 componentWillUpdate 等方法才能帮助 ReactJS 框架猜对。

inline-block的问题

观察上面的例子,细心的同学肯定会发现,每个li之间有一个小空隙,而我们的代码中并没有设置margin等相关属性,这是为什么呢?

区分开发和生产环境

大致的目录结构是这样的:

+ build + config + src

1
2
3
+ build
+ config
+ src

在 build 目录下有四个 webpack 的配置。分别是:

  • webpack.base.conf.js
  • webpack.dev.conf.js
  • webpack.prod.conf.js
  • webpack.test.conf.js

这分别对应开发、生产和测试环境的配置。其中 webpack.base.conf.js 是一些公共的配置项。我们使用 webpack-merge 把这些公共配置项和环境特定的配置项 merge 起来,成为一个完整的配置项。比如 webpack.dev.conf.js 中:

'use strict' const merge = require('webpack-merge') const baseWebpackConfig = require('./webpack.base.conf') const devWebpackConfig = merge(baseWebpackConfig, { ... })

1
2
3
4
5
6
7
'use strict'
const merge = require('webpack-merge')
const baseWebpackConfig = require('./webpack.base.conf')
 
const devWebpackConfig = merge(baseWebpackConfig, {
   ...
})

这三个环境不仅有一部分配置不同,更关键的是,每个配置中用 webpack.DefinePlugin 向代码注入了 NODE_ENV 这个环境变量。

这个变量在不同环境下有不同的值,比如 dev 环境下就是 development。这些环境变量的值是在 config 文件夹下的配置文件中定义的。Webpack 首先从配置文件中读取这个值,然后注入。比如这样:

build/webpack.dev.js

plugins: [ new webpack.DefinePlugin({ 'process.env': require('../config/dev.env.js') }), ]

1
2
3
4
5
plugins: [
  new webpack.DefinePlugin({
    'process.env': require('../config/dev.env.js')
  }),
]

config/dev.env.js

module.exports ={ NODE_ENV: '"development"' }

1
2
3
module.exports ={
  NODE_ENV: '"development"'
}

至于不同环境下环境变量具体的值,比如开发环境是 development,生产环境是 production,其实是大家约定俗成的。

框架、库的作者,或者是我们的业务代码里,都会有一些根据环境做判断,执行不同逻辑的代码,比如这样:

if (process.env.NODE_ENV !== 'production') { console.warn("error!") }

1
2
3
if (process.env.NODE_ENV !== 'production') {
  console.warn("error!")
}

这些代码会在代码压缩的时候被预执行一次,然后如果条件表达式的值是 true,那这个 true 分支里的内容就被移除了。这是一种编译时的死代码优化。这种区分不同的环境,并给环境变量设置不同的值的实践,让我们开启了编译时按环境对代码进行针对性优化的可能。

AngularJS的脏检查

除了类似 ReactJS 的虚拟 DOM 机制,其他流行的框架,比如 AngularJS 还会使用脏检查算法来渲染页面。

类似 AngularJS 的脏检查算法和 ReactJS 有一样的缺点,无法得知状态修改的意图,必须完整重新计算 View 模板。除此之外,AngularJS 更新 DOM 的范围往往会比实际所需大得多,所以会比 ReactJS 还要慢。

默认的inline元素

首先,我们观察一下默认的inline元素的表现:

HTML代码

XHTML

<a>首页</a> <a>热点</a>

1
2
<a>首页</a>
<a>热点</a>

CSS代码

CSS

a { margin: 0; padding: 0; border: 1px solid #000; }

1
2
3
4
5
a {
    margin: 0;
    padding: 0;
    border: 1px solid #000;
}

效果图

图片 2

inline默认情况

默认情况下,inline元素之间就有空隙出现,所以结合了inline和block属性的inline-block属性自然也有这个特点。
那这些空隙是什么呢,它们是空白符!

Code Splitting && Long-term caching

Code Splitting 一般需要做这些事情:

  • 为 Vendor 单独打包(Vendor 指第三方的库或者公共的基础组件,因为 Vendor 的变化比较少,单独打包利于缓存)
  • 为 Manifest (Webpack 的 Runtime 代码)单独打包
  • 为不同入口的公共业务代码打包(同理,也是为了缓存和加载速度)
  • 为异步加载的代码打一个公共的包

Code Splitting 一般是通过配置 CommonsChunkPlugin 来完成的。一个典型的配置如下,分别为 vendor、manifest 和 vendor-async 配置了 CommonsChunkPlugin。

new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', minChunks (module) { return ( module.resource && /.js$/.test(module.resource) && module.resource.indexOf( path.join(__dirname, '../node_modules') ) === 0 ) } }), new webpack.optimize.CommonsChunkPlugin({ name: 'manifest', minChunks: Infinity }), new webpack.optimize.CommonsChunkPlugin({ name: 'app', async: 'vendor-async', children: true, minChunks: 3 }),

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      minChunks (module) {
        return (
          module.resource &&
          /.js$/.test(module.resource) &&
          module.resource.indexOf(
            path.join(__dirname, '../node_modules')
          ) === 0
        )
      }
    }),
 
    new webpack.optimize.CommonsChunkPlugin({
      name: 'manifest',
      minChunks: Infinity
    }),
 
    new webpack.optimize.CommonsChunkPlugin({
      name: 'app',
      async: 'vendor-async',
      children: true,
      minChunks: 3
    }),

CommonsChunkPlugin 的特点就是配置比较难懂,大家的配置往往是复制过来的,这些代码基本上成了模板代码(boilerplate)。如果 Code Splitting 的要求简单倒好,如果有比较特殊的要求,比如把不同入口的 vendor 打不同的包,那就很难配置了。总的来说配置 Code Splitting 是一个比较痛苦的事情。

而 Long-term caching 策略是这样的:给静态文件一个很长的缓存过期时间,比如一年。然后在给文件名里加上一个 hash,每次构建时,当文件内容改变时,文件名中的 hash 也会改变。浏览器在根据文件名作为文件的标识,所以当 hash 改变时,浏览器就会重新加载这个文件。

Webpack 的 Output 选项中可以配置文件名的 hash,比如这样:

output: { path: config.build.assetsRoot, filename: utils.assetsPath('js/[name].[chunkhash].js'), chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') },

1
2
3
4
5
output: {
  path: config.build.assetsRoot,
  filename: utils.assetsPath('js/[name].[chunkhash].js'),
  chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
},

Binding.scala的精确数据绑定

Binding.scala 使用精确数据绑定算法来渲染 DOM 。

在 Binding.scala 中,你可以用 @dom 注解声明数据绑定表达式。@dom 会自动把 = 之后的代码包装成 Binding 类型。

比如:

@dom val i: Binding[Int] = 1 @dom def f: Binding[Int] = 100 @dom val s: Binding[String] = "content"

1
2
3
@dom val i: Binding[Int] = 1
@dom def f: Binding[Int] = 100
@dom val s: Binding[String] = "content"

@dom 既可用于 val 也可以用于 def ,可以表达包括 IntString 在内的任何数据类型。

除此之外,@dom 方法还可以直接编写 XHTML,比如:

@dom val comment: Binding[Comment] = <!-- This is a HTML Comment --> @dom val br: Binding[HTMLBRElement] = <br/> @dom val seq: Binding[BindingSeq[HTMLBRElement]] = <br/><br/>

1
2
3
@dom val comment: Binding[Comment] = <!-- This is a HTML Comment -->
@dom val br: Binding[HTMLBRElement] = <br/>
@dom val seq: Binding[BindingSeq[HTMLBRElement]] = <br/><br/>

这些 XHTML 生成的 Comment 和 HTMLBRElement 是 HTML Node 的派生类。而不是 XMLNode。

每个 @dom 方法都可以依赖其他数据绑定表达式:

val i: Var[Int] = Var(0) @dom val j: Binding[Int] = 2 @dom val k: Binding[Int] = i.bind * j.bind @dom val div: Binding[HTMLDivElement] = <div>{ k.bind.toString }</div>

1
2
3
4
val i: Var[Int] = Var(0)
@dom val j: Binding[Int] = 2
@dom val k: Binding[Int] = i.bind * j.bind
@dom val div: Binding[HTMLDivElement] = <div>{ k.bind.toString }</div>

通过这种方式,你可以编写 XHTML 模板把数据源映射为 XHTML 页面。这种精确的映射关系,描述了数据之间的关系,而不是 ReactJS 的 render 函数那样描述运算过程。所以当数据发生改变时,只有受影响的部分代码才会重新计算,而不需要重新计算整个 @dom 方法。

比如:

val count = Var(0) @dom def status: Binding[String] = { val startTime = new Date "本页面初始化的时间是" + startTime.toString + "。按钮被按过"

  • count.bind.toString + "次。按钮最后一次按下的时间是" + (new Date).toString } @dom def render = { <div> { status.bind } <button onclick={ event: Event => count := count.get + 1 }>更新状态</button> </div> }
1
2
3
4
5
6
7
8
9
10
11
12
13
val count = Var(0)
 
@dom def status: Binding[String] = {
  val startTime = new Date
  "本页面初始化的时间是" + startTime.toString + "。按钮被按过" + count.bind.toString + "次。按钮最后一次按下的时间是" + (new Date).toString
}
 
@dom def render = {
  <div>
    { status.bind }
    <button onclick={ event: Event => count := count.get + 1 }>更新状态</button>
  </div>
}

以上代码可以在ScalaFiddle实际运行一下试试。

注意,status 并不是一个普通的函数,而是描述变量之间关系的特殊表达式,每次渲染时只执行其中一部分代码。比如,当 count 改变时,只有位于 count.bind 以后的代码才会重新计算。由于 val startTime = new Date 位于 count.bind 之前,并不会重新计算,所以会一直保持为打开网页首次执行时的初始值。

有些人在学习 ReactJS 或者 AngularJS 时,需要学习 keyshouldComponentUpdate$apply$digest 等复杂概念。这些概念在 Binding.scala 中根本不存在。因为 Binding.scala 的 @dom 方法描述的是变量之间的关系。所以,Binding.scala 框架知道精确数据绑定关系,可以自动检测出需要更新的最小部分。

本文由澳门新葡亰手机版发布于web前端,转载请注明出处:配置最佳实践

上一篇:高性能滚动,该何去何从 下一篇:了解一些额外知识,的人都理解错了HTTP中GET与
猜你喜欢
热门排行
精彩图文