Webpack构建原理

现代前端工程化:ES6 + Webpack + React + Babel,commonjs 的模块必须在使用前经过 webpack 的构建才可以被游览器直接使用,webpack是现代前端重要一环。

webpack解决的问题

当项目很复杂的时候,没有模块系统会产生很多复杂问题,模块化相关如下:http://bugzhang.com/2017/08/01/javascriptmo-kuai-hua/ webpack可以打包所有静态资源,常见的例如:

  • 根据模板生成html,并且自动处理上面的css/JS引用路径
  • 自动处理img里图片路径,css样式中背景图的路径,字体引用
  • 开启本地服务器,变写代码边自动更新页面内容watch
  • 编译jsx、es6、sass、less、coffeescript等等并添加md5、sourcemap等辅助
  • 异步加载内容,比如弹出框,不需要时不加载到dom
  • 配合vue.js、react等框架,解析相关文件

webpack整体架构

以webpack.config主要部分划分,webpack主要有以下部分:

  • entry:一个可执行模块的入口文件
  • output:模块的出口目录
  • chunk:多个文件组成的一个代码块
  • loader:模块转换器,确定处理方式
  • plugin:插件,拓展webpack功能,与loader一同定义webpack的处理方式
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
module.exports = {
devtool: isProd
? false
: '#cheap-module-source-map',
output: {
path: path.resolve(__dirname, '../dist'),
publicPath: '/dist/',
filename: '[name].[chunkhash].js'
},
resolve: {
alias: {
'public': path.resolve(__dirname, '../public')
}
},
module: {
noParse: /es6-promise\.js$/, // avoid webpack shimming process
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: vueConfig
},
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/
},
{
test: /\.(png|jpg|gif|svg)$/,
loader: 'url-loader',
options: {
limit: 10000,
name: '[name].[ext]?[hash]'
}
},
{
test: /\.css$/,
use: isProd
? ExtractTextPlugin.extract({
use: 'css-loader?minimize',
fallback: 'vue-style-loader'
})
: ['vue-style-loader', 'css-loader']
}
]
},
performance: {
maxEntrypointSize: 300000,
hints: isProd ? 'warning' : false
},
plugins: isProd
? [
new webpack.optimize.UglifyJsPlugin({
compress: { warnings: false }
}),
new ExtractTextPlugin({
filename: 'common.[chunkhash].css'
})
]
: [
new FriendlyErrorsPlugin()
]
}

一个典型的webpack配置文件

webpack构建过程

官网对webpack构建的图示如下: webpack会将所有静态资源看做是模块,然后把这些模块组成到一个bundle,在页面上最终引入一个bundle.js实现对静态资源的加载。大概过程如下: 构建过程中,webpack做了很多工作,主要的有:

  • 读取并初始化option
  • 编译
  • 递归分析依赖,按照依赖build
  • 构建,构建过程中会用相应的loader
  • 构建完毕后编译,生成AST抽象语法树
  • 遍历AST,在有依赖时,收集依赖
  • 打包前合并、压缩等
  • 输出文件

正则表达式总结

常规

  • .:任意字符,行结束符除外,要匹配行结束符,使用[\s\S]
  • \d:任意数字,\D任意非数字
  • \w:字母数字类字符,\W任意非字母数字
  • \s:空白字符(空格、制表符、换行符、回车符、翻页符),\S任意不是空白符的字符
  • \b:匹配单词的开始或结束,\B 匹配不是单词开头或结束的位置
  • ^:匹配字符串的开始
  • $:匹配字符串的结束

量词

  • *:重复零次或更多次
  • +:重复一次或更多次
  • ?:重复零次或一次
  • {n}:重复n次
  • {n,}:重复n次或更多次
  • {n,m}:重复n到m次

分组

  • (<< pattern >>):捕获组,反向引用访问或者匹配操作的结果
  • (?:<< pattern >>):非捕获组,不保存捕获内容

断言

  • (?=exp):匹配exp前面的位置
  • (?<=exp):匹配exp后面的位置
  • (?!exp):匹配后面跟的不是exp的位置
  • (?< !exp):匹配前面不是exp的位置

注释

  • (?#comment):这种类型的分组不对正则表达式的处理产生任何影响,用于提供注释

JavaScript模块化

从古代前端说起

早起的前端,是没有工程体系的,基本就是div+css,以及用js做个动画验证个表单之类的工作,因为工程不复杂,变量少,代码重构难度低,开发团队也不大(当时很多前端都是后端甚至设计师来写),所以这门语言一开始对模块化的需求不大,可以统统全局变量,反正没几个。在需要的时候写端脚本然后引入即可,甚至很多js代码是混入html属性中也不足为奇。

模块

而对于javascript来说,模块代表就是一个特定函数,这种模式使用了闭包

1
2
3
4
5
6
7
8
9
var module =
(function() {
//私有变量 函数
...
return{
//公有函数
...
};
})();

这种javascript模块,将内部功能与对外功能划定了清晰的界限。防止了命名冲突。

CommonJS

CommonJS认为js不应该只是简单脚本语言,应该可以运行在任何地方,因此开始制定一系列api。CommonJS的崛起主要是随着node.js的大热,而被熟知了的。

1
2
3
4
5
6
7
8
//定义模块
exports.sum = {};

//使用模块
var xxx = require('xxx');
exports.add = function(n){
return xxx.ss(val,n);
};

这种写法适合服务端,因为在服务器读取模块都是在本地磁盘,加载速度很快。但是如果在客户端,加载模块的时候有可能出现“假死”状况。因为使用时候必须等模块加载完毕。但即使如此,实际还是不能使用的主要原因是少了node中才有的几个全局变量:

  • module
  • exports
  • require
  • global

现在,browserify和webpack,通过commonjs在游览器的实现,可以进行模块打包处理,其中webpack已经成为了前端工程标准的打包工具。

AMD

AMD,即 (Asynchronous Module Definition),异步模块定义,require.js应用的是这一规范。先定义所有依赖,然后在加载完成后的回调函数中执行。

1
2
//使用模块
require([module], callback);

CMD

CMD, Common Module Definition,命令模块定义。实现是淘宝玉伯大神开发的sea.js相关的。 CMD和AMD一样也是异步的,其使用方式是:

1
2
3
4
5
//模块的定义
define(function(require, exports, module) {
var xxx = require('xxx');
xxx.start();
});

AMD和CMD最大的区别是对依赖模块的执行时机处理不同。 CMD对模块的态度是懒执行, 而AMD对模块的态度是预执行。CMD是在需要的时候才会执行,带来的好处就是开发者能理解因为过程更接近与人脑所想,而AMD是所有的模块全部提前,带来结果就是如果不考虑这点就会被坑。可以说CMD通过机器打包的效率提升了编码效率,这可能也是sea.js在webpack但是之前在国内更火的原因。

使用Web Worker

js作为单线程的语言,无法同时运行多个处理程序,但面对一些不得不耗费资源才能完成的任务时,单线程弊端就显现出来,为了解决这个问题,Wob Worker应身而出:“Worker”是指执行代码的并行线程,可以在一个不与用户交互的独立环境中,就行一些耗资源任务的处理,与游览器文档中的js相当于多线程,比如图像处理等任务。Web Worker有效解决了js的缺点,同时是提升性能的一个方法之一:将耗资源的代码在worker中执行。

worker对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//创建worker对象并执行
var worker = new Worker("js/decrypt.js");

//添加事件处理程序,完成后给主线程发送信息时执行
worker.onmessage = function(e){
console.log('finash');
}

//发送信息给worker
worker.postMessage('file.txt');

//注册用来接收来自主线程信息的处理程序
worker.onmessage = function(e){
//从事件对象获取消息
var valueToDecrypt = e.data;
//进行操作
console.log('end');
//返回给主线程
postMessage(decryptedValue);
}

Worker执行模型与作用域

Worker线程从上到下执行代码与文件,如果Worker注册了onmessage事件,只要message事件可能触发,将永远不会退出。 一个Worker对象是运行在完全隔离的环境中,其作用域大于主线程所在的JavaScript全局对象,小于整个客户端的Window对象。

使用gulp减少HTTP请求

游览器与服务器每进行一个http连接,需要进行TCP三次握手的确认,如果是https更是多出了7次安全确认来完成连接。每个http报文中,又有很多重复的多余信息,因此减少http请求,是最为可行的前端优化方式之一。以下是现代前端中,常用的减少http请求的方式及其实现。

CSS Sprites

CSS Sprites,又叫雪碧图或者CSS精灵,是指把多个图片合并在一张图片上,然后通过background-positon属性制定CSS prites偏移量。 因为实现很简单,这个就不粘代码了。
总之雪碧图可以让项目变得:更少的图片,干净的标签,更少的HTTP请求,甚至更小的文件(因为图片压缩编码的原因,合并后的文件往往比分开的文件总和要小,空白区域并不会增加额外的大小)。总而言之使用CSS Sprites可以提升网站性能。

合并js脚本与css样式表

每多合并一个js脚本或者css样式表,就可以减少一次http请求,进而提升性能。对于现代前端而言,合并脚本与样式表的工作,往往交给自动化工具完成,我常用的是Gulp,下边是使用Gulp自动化工具进行压缩的配置文件gulpfile.js的示例:

const gulp = require('gulp');
const concat = require('gulp-concat');
const concatCss = require('gulp-concat-css');

gulp.task('testConcat', function () {
    gulp.src('src/js/*.js')
        .pipe(concat('main.js'))        //合并后的文件名
        .pipe(gulp.dest('dist/js'));
});

gulp.task('testConcatCss', function () {
  return gulp.src('assets/**/*.css')
    .pipe(concatCss("styles/bundle.css"))
    .pipe(gulp.dest('out/'));
});

附gulp中文文档:www.gulpjs.com.cn

将小图片进行base64编码后写入css

用编码工具,将小图片进行base64编码,然后将编码后的数据拷贝出来,放在background-image: url的位置。该方法也同样减少了http请求,但会增大体积,故不适合于大图片仅适合于小图片。实现同样很简单,就不写了。

采用icon-font的方式引入矢量图标

实现很简单,把svg丢到一个自动化的工具里就可以打包出来了,使用通过css的类来使用,相信用过bootstrap等此类响应式框架的同学都知道。我常用的打包网站是:https://icomoon.io/ 。个人感觉,这是最适合的对图片的处理方式,未来小图片将全部矢量化。

重绘和回流

游览器渲染过程

渲染过程发生在通过http得到资源后的游览器内部,其过程如下:

  • 游览器首先对HTML进行解析,这部分由游览器的HTML文档引擎实现,如KHTML(其细节与原理以后有时间会分析源码后再写一篇),这时得到dom元素并生产DOM树(dom tree),每一个HTML标签都会在DOM树有对应的节点,根节点就是document对象,对应元素。

  • 于此同时,游览器会对CSS进行解析,其解析优先级为:以style属性书写的CSS -> 用户link引入的CSS -> 用户import引入的CSS -> 游览器默认的CSS规则,并且考虑CSS覆盖于优先级,忽略不能被解析的错误CSS与无意义的前缀名,保留有意义的可被正确解析的CSS。

  • CSS解析完毕后,通过之前生成的渲染树加上解析完毕后的CSS,就能构建一课渲染树(render tree),每一个元素一般会对应一个节点,不一般的情况大家也能想到就是通过display:none进行修饰的元素就不会出现先渲染树中(敲黑板:因此也就不会有物理空间了,笔试常考),以及某些可以换行的块级元素&lt;p&gt;是一行一个节点。

  • 完成渲染树后,游览器开始绘制页面。


重绘和回流

  • 重绘:当渲染树中的元素样式改变不影响布局时,发生重绘,重绘不会改变整课渲染树,只会改变部分渲染树的节点属性,如颜色,风格,字体样式,阴影,模糊,背景等。
  • 回流:当渲染树种元素的样式改变影响了布局时,发生回流,回流会让页面重新构造,并让受到影响的部分失效,完成回流后,游览器会重新展示受影响的部分,这个过程也是重绘。所以发生回流是一定会发生重绘的。引起回流的样式改变有:
    • 添加或删除可见的dom元素;
    • 修改了可见dom元素的大小、位置、布局、隐藏(display:none会回流,visibility: hidden不会回流);
    • 修改了可见元素的内容,需要重新计算,如改变图片的src,改变文字的数量;
    • 游览器的窗口变化,改变缩放比例,改变字号,滚动;
    • 页面渲染初始化;
    • 添加了一个样式表其中有引起回流的样式;

前端优化之减少重绘与回流

通过上面分析,已经熟悉了重绘与回流的概念和过程,那么其优化无非就是减少引起重绘和回流的样式改变。

  • 使用改变类名的方式改变样式,可以一次性将多个样式改变,从而减少重绘和回流的产生次数,同时也增加了页面的可维护性。
  • 使用display:none隐藏元素
  • 尽量不要计算元素
  • 能用重绘解决的就不要用回流解决

深信服实习面试

一面

1.介绍自己,介绍项目,重点介绍项目里所用技术(angular.js,vue.js,bootstrapt,npm,glup),项目过程中遇上的问题以及解决方案,因为参与开发的网站都上线运行,所以这块很顺利。 2.面试官根据简历内容提问,重点在计算机基础知识。 3.简历写了熟悉vue了解ng:询问前端三大框架对比,前端路由(vue-route)。 4.简历写了了解node和express框架:询问Node.js的异步非阻塞,牵扯到操作系统(进程线程、异步同步、阻塞非阻塞)。 5.简历写了了解nginx:说下正向代理与反向代理区别(本来知道的,当时紧张两次说成一样了,被反问后也没反应过来然后就被问了下一个)。 6.简历写了熟悉linux下开发:询问改变权限命令,具体说到421规则和RWX。 7.简历写了熟悉Git版本控制:问用的GUI还是命令行?我说命令行,询问了拉取,提交和创建分支的命令。 8.简历写了了解mongo和mysql数据库:问说明这两个数据库区别,分别适用于那种情况 9.问我专业?计算机科学与技术。学了那些课程?数据结构与算法学了吧?学了。写一个希尔排序(不会),写一个堆排序(不会),写一个快速排序。 10.TCP三次握手。 后来知道一面面试官是服务器和VPN领域比较有名的一个技术专家,写过好几本相关的书。

二面(60分钟)

一面结束后,面试官让我稍等,因为一面问题比较简单所以觉得应该能稳过。果然面试官出去大概十分钟后,二面面试官进来开始二面,期间和gay平聊了下q。 1.HTTP协议:HTTP请求头,HTTP请求方式哪几种以及区别,cookie在哪里,什么特点,服务器那边对应的是什么,除了cookie还有那些常见的客户端存储。HTTP相应头,状态码。 2.HTTPS协议:说完SSL与TLS后自己又拓展把细节介绍了下,对称密钥与非对称密钥,https优缺点,https证书cname配置。 3.background实现一个简单效果,我一直试图用svg和iconfont实现,后来才知道人家只问background,然后我解释用svg和iconfont可以减少请求与大小优化性能 4.盒子模型,margin负值实现简单效果,position属性 5.水平垂直居中一个div。 6.三栏布局,我说了flex布局然后拓展说了bootstrapt的原理(3代以前媒体查询+浮动+绝对定位,四代开始用flex) 7.js基本类型,说完六种后我说还有symbol,常见的引用类型 8.Array api 9.dom实际操作:生成子元素并赋予id,修改css,删除一些节点等等类似简单问题 10.看到我会数据库,打算让我设计一个系统的数据库,因为我怕被问到不擅长的地方,直接说不是很擅长数据库设计,然后他给我画了几张表。问我相关sql语句。 11.一个页面到一个数据库中间会发生一种安全隐患,是什么:sql注入,解决方案转义、存储过程、检查类型,危害。又告诉他xss也可以被发生,只要有input就可以xss,之后介绍xss分类,解决方案与危害13.因为xss说道跨域,解释了游览器同源策略,前端跨域解决方案,cors,jsonp,图像p 12.你的数据库经常用的什么做主键?自增。我指的是数据类型?Number,我用mongo。会关系型吧?会,mysql用int之类,和number一样的。连外键后主键冲突怎么解决?uuid(),mongo有默认主键_id不会冲突,生成和时间进程机器有关。 13.用什么游览器?chrome。调试怎么调试?console.log和断点具体细节以及为何不用alert。有没有用过抓包工具?没有用过专门的,前端性能优化靠chrome自带的开发者工具就够了

三面(20分钟)

十几分钟后,一面面试官回来,告诉我还有一个三面,三面是深圳那边电面,明天早上来电话。 一早上没来电话,下午三点我在实验室沙发睡觉,来了一个深圳电话,晕晕乎乎接了电话,面试官解释早上有些忙不好意思,我同时找眼镜找了半天最后还是没找到(八百度近视的我只有和我差不多的人才能体会不戴眼镜的痛苦)。开始三面,从自我介绍开始一直卡到项目介绍。好在问的很水。 1.自我介绍 2.项目介绍,技术选型,上线了没,现在就能访问,给了地址 3.你大三实习不方便吧,能解决不 4.感觉你对新技术关注度很高,技术选型比较新,平时怎么学这些,上那些网站,看了那些书,网站关于什么,书关于什么 5.写博客么?我给了我博客地址,写了多少篇?从15年1月写到现在,大概30多篇。自己写的还是的转载别人的?有一些是学习笔记,大多数是算自己写的吧。 6.参与过开源项目么,没有。虽然我有四年使用的github,但只是作为个人项目的一个代码托管工具以及方便部署项目,目前技术太水没有做开源的能力,以后有能力会尽量参与。 7.聊人生 8.怎么知道公司的 9.有没有其他问题问他

2016.11.2 记一次黑客松

10月30号到31号两天,和萌天、飞飞、zp,去武汉参加了光谷社区主办的黑客松,最后开发一个寻找周围网速最快咖啡厅的应用的想法,结合实时渲染三维地图+数据查找的应用,起名为CafeGo。 <

吐槽和感想

当时天哥很忙,当时手上外包还没有结束,不过最后还是带我们去搞了。当时报名队伍很多,远远多于去年,似乎是16年开始黑客松在国内兴起的原因。安排在一个大电气厂房,并且估计是因为DHCP虚拟IP不够用的原因,一大半开发者全程没网,根本无法实现服务和查询资料。最后基本是去外边宾馆完成了。

天哥正在调试开发,被摄影师抓拍

开发过程中,发现自己在解决实际项目或者说是领域内未接触过的内容的开发时,战力基本为0,忍不住膜拜天哥的能力。
因为是在武汉的原因,除了武汉本地公司的程序员参加外,还有很多华科的团队参加,冰岩作坊、联创和dian这几个大牛团队都在,和中南这边的开发者相比,华科开发者能力完全不是中南所能理解(除了天哥这种个别几个强人之外)。或者说,人家看我们大概就是我们看朝鲜一样的技术。当时中南参加的除了我们,还有一些人,在我作为一个低水平web开发看来,他们做的网站难度和创意,至少前端的实现还是处于相当低级的水平(典型jQuery+bootstrap切图工)。所以在这种环境中,如果你不能像天哥那样碾压掉所有人,那么能力已经注定不会太高。

左起是zp,天哥,我,飞哥。我当时正在讲产品。

技术实现

应用服务端比较简单,采用node.js+mongodb的架构,提供RESTful的API返回json,数据库中主要包含咖啡屋地名,用户提交次数,经纬度,网速等几个字段。当客户端以发出请求后,可以响应咖啡馆的信息,客户端进行数据的解析与处理并实时渲染出所需。同事,客户端可以将自己目前的网速上传进服务器,用于数据共享,共同组建出足够大的数据库。另外,如果更新时已有数据,取最近的几个数据均值作为最终网速。 模式图如下: 客户端相对比较复杂,主要有以下解决点:

  • 根据数据实施建模,有较好网速区分度?最后我们用绿黄红三种颜色的立体柱形外加与网速绑定的柱高来明显区分,并在旁边标注出咖啡厅名子与具体网速。
  • 本地网速数据的获取与上传。
  • 基于VR导航系统。

考虑到三维方面的需求和有VR功能的需求,并且要高效高速完成开发(只有不到三十小时开发)。最后决定移动客户端上,采用Unity3d完成,之后打包成为iOS与Android应用。网页端使用Mapbox的API完成。

效果预览

预览视频:http://v.qq.com/x/page/g0344t8vf3h.html