10

1,Webpack有什么作用,谈谈你对它的理解

现在的前端网页功能丰富,特别是SPA(single page web application 单页应用)技术流行后,JavaScript的复杂度增加和需要一大堆依赖包,还需要解决Scss、Less……新增样式的扩展写法的编译工作。Webpack 最核心的功能就是实现静态模块打包,当Webpack 处理应用程序时,会递归构建一个依赖关系图,其中包含应用程序需要的每个模块,然后将这些模块打包成一个或多个bundle资源包。

事实上,现在市面上最流行的三个前端框架,可以说和webpack都已经紧密相连,官方框架都推出了对应的webpack构建工具,常见的组合为React+WebPack、Vue+WebPack以及Angluar+WebPack。

2,前端项目为什么要进行打包和构建

之所以要进行打包和构建,是从代码开发层面和部署层面来考虑的:

代码层面

  • 打包可以使体积更小(Tree-shaking、压缩、合并),加载更快
  • 编译高级语言和语法(TS、ES6、模块化、scss)
  • 兼容性和错误检查(polyfill、postcss、eslint)

研发流程层面

  • 统一、高效的开发环境
  • 统一的构建流程和产出标准
  • 集成公司构建规范(提测、上线)

3,gulp/grunt与Webpack有哪些区别

三者都是前端构建工具,grunt和gulp在早期比较流行,现在webpack相对来说比较主流,不过一些轻量化的任务还是会用gulp来处理,比如单独打包CSS文件等。

grunt和gulp是基于任务和流(Task、Stream)的。类似jQuery,找到一个(或一类)文件,对其做一系列链式操作,更新流上的数据, 整条链式操作构成了一个任务,多个任务就构成了整个web的构建流程。

而webpack的项目构建则是基于入口的,webpack会自动地递归解析入口所需要加载的所有资源文件,然后用不同的Loader来处理不同的文件,用Plugin来扩展webpack功能。
 

从构建规则上来说,gulp和grunt需要开发者将前端构建过程拆分多个Task,并合理控制所有Task的调用关系,webpack需要开发者找到入口,并需要清楚对于不同的资源应该使用什么Loader 做何种解析和加工的。

 

4,Webpack提供的基本功能有哪些

目前,Webpack支持如下一些常见的功能(具体视版本而定):

  • 代码转换:TypeScript 编译成 JavaScript、SCSS 编译成 CSS 等等
  • 文件优化:压缩 JavaScript、CSS、html 代码,压缩合并图片等
  • 代码分割:提取多个页面的公共代码、提取首屏不需要执行部分的代码让其异步加载
  • 模块合并:在采用模块化的项目有很多模块和文件,需要构建功能把模块分类合并成一个文件
  • 自动刷新:监听本地源代码的变化、自动构建、刷新浏览器
  • 代码校验:在代码被提交到仓库前需要检测代码是否符合规范,以及单元测试是否通过
  • 自动发布:更新完代码后,自动构建出线上发布代码并传输给发布系统。

5,简单描述下webpack的构建流程

Webpack的运行流程是一个串行的过程,从启动到结束会会依次执行以下流程:

  1. 初始化编译参数:从配置文件和shell命令中读取与合并参数;
  2. 开始编译:根据上一步得到的参数初始化Compiler对象,加载所有配置的Plugin,执行对象的 run 方法开始执行编译;
  3. 确定入口:根据配置中的 entry 找出所有的入口文件;
  4. 编译模块:从入口文件触发,调用所有配置的Loader对模块进行翻译,再找出该模块依赖的模块,然后递归本步骤直到所有入口依赖的文件都进行翻译;
  5. 完成模块编译:在经过第4步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系图。
  6. 输出资源:根据依赖关系图,组装成一个个包含多个模块的Chunk,再把每个Chunk转化成一个单独的文件加入到输出列表,根据配置确定输出的路径和文件名,输出。
  7. 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统。

在以上过程中,Webpack 会在特定的时间点广播出特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用 Webpack 提供的 API 改变 Webpack 的运行结果。整体过程如下图所示。

image.png

 

6,简单介绍下Webpack的基本概念

  • Entry:入口,Webpack 执行构建的第一步将从 Entry 开始,告诉webpack要使用哪个模块作为构建项目的入口,默认为./src/index.js。
  • output :出口,告诉webpack在哪里输出它打包好的代码以及如何命名,默认为./dist
  • Module:模块,在 Webpack 里一切皆模块,一个模块对应着一个文件。Webpack 会从配置的 Entry 开始递归找出所有依赖的模块。
  • Chunk:代码块,一个 Chunk 由多个模块组合而成,用于代码合并与分割。
  • Loader:模块转换器,用于把模块原内容按照需求转换成新内容。
  • Plugin:扩展插件,在 Webpack 构建流程中的特定时机会广播出对应的事件,插件可以监听这些事件的发生,在特定时机做对应的事情。

 

7,简单描述下Webpack的工作原理

webpack通过入口文件逐层遍历到模块依赖,进行代码分析、代码转换,最终生成可在浏览器运行的打包后代码。本质上,webpack 是一个现代 JavaScript 应用程序的静态模块打包器。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图,其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个bundle包。并且在3.0版本之后,Webpack还肩负起了优化项目的责任。
 

8,如何提高Webpack的构建速度

随着前端项目涉及到的页面越来越多,功能和业务代码也会随着增加,相应的webpack的构建时间也会越来越久,如果构建时间过长,会大大降低工作效率。以下是常见的提高Webpack项目构建的措施:

  • 多入口情况下,使用CommonsChunkPlugin来提取公共代码。
  • 通过externals配置来提取常用库。
  • 利用DllPlugin和DllReferencePlugin预编译资源模块通过DllPlugin来对那些我们引用但是绝对不会修改的npm包来进行预编译,再通过DllReferencePlugin将预编译的模块加载进来。
  • 使用Happypack 实现多线程加速编译。
  • 使用webpack-uglify-paralle来提升uglifyPlugin的压缩速度,webpack-uglify-parallel采用了多核并行压缩来提升压缩速度。
  • 使用Tree-shaking和Scope Hoisting来剔除多余代码
  • 优化Loader配置

 

9,Webpack是怎么配置单页应用和多页面应用的

单页应用可以理解为Webpack的标准模式,直接在entry中指定单页应用的入口即可。多页应用的话,可以使用webpack的 AutoWebPlugin来完成简单自动化的构建,但是前提是项目的目录结构必须遵守他预设的规范。多页应用中需要注意以下几点:

  • 每个页面都有公共的代码,可以将这些代码抽离出来,避免重复的加载。比如,每个页面都引用了同一套css样式表。
  • 随着业务的不断扩展,页面可能会不断的追加,所以一定要让入口的配置足够灵活,避免每次添加新页面还需要修改构建配置。

 
事实上,单页应用程序和多页应用程序的 webpack配置文件其实绝大部分都还是相同的,只不过多页的配置需要在单页配置的基础上顾及到多个页面罢了,loader、output、plugins这些基本都不需要改动,需要改动的一般都是入口文件 entry,如果你用到了 抽离css样式的插件 extract-text-webpack-plugin、自动模板插件  html-webpack-plugin的话,那么还需要对这两个插件进行额外的改写。

10,Webpack中Loader的作用是什么,有哪些常见的Loader

Loader 是webpack中提供了一种处理多种文件格式的机制,因为Webpack默认只支持识别JS和JSON,但是Webpack打造的概念是“一切皆模块”,想要实现这个概念,就需要使用Loader。Loader相当于翻译官,可以将其他类型资源进行预处理,并对模块的"源代码"进行转换。

常见的Loader有如下一些:

  • less-loader:将less文件编译成css文件
  • css-loader:将css文件变成commonjs模块加载到js中,模块内容是样式字符串
  • style-loader:创建style标签,将js中的样式资源插入标签内,并将标签添加到head中生效
  • ts-loader:打包编译Typescript文件
  • url-loader:将文件转换为base64 URI
  • source-map-loader:加载额外的 Source Map ⽂件,以⽅便断点调试
  • babel-loader:将ES6转化为ES5
  • file-loader:把⽂件输出到⼀个⽂件夹中,在代码中通过相对 URL 去引⽤输出的⽂件

11,Plugin有什么作用,有哪些常见的Plugin

Plugin的主要做用就是为了解决Loader 无法实现的事情,比如打包优化和代码压缩等。通过扩展 Webpack 功能,在构建流程里注入钩子来处理很多Loader无法处理的工作,给 Webpack 带来了很大的灵活性。

通常,Webpack通过plugins属性来配置需要使用的插件列表。plugins属性是一个数组,里面的每一项都是插件的一个实例,在实例化一个组件时可以通过构造函数传入这个组件支持的配置属性。

常见的Plugin插件有如下一些:

  • html-webpack-plugin:处理html资源,默认会创建一个空的HTML,自动引入打包输出的所有资源(js/css)。
  • mini-css-extract-plugin:打包过后的css在js文件里,该插件可以把css单独抽出来。
  • clean-webpack-plugin:每次打包时候,CleanWebpackPlugin插件就会自动把上一次打的包删除。

12,简述下Webpack sourceMap的作用

当我们使用webpack打包代码出错的时候, 如果不使用sourceMap,我们只能知道打包后的代码第几行出错,并不知道对应源代码哪里出了错,所以需要它来做源代码和目标代码的映射,而sourceMap的主要作用就是灾打包后将文件映射到源代码,用于定位错误位置。sourceMap的配置如下所示:

module.exports = {
    devtool: 'source-map', // 使用sourceMap
    ...
}

同时,加不同前缀的意义也不同:

  • inline:不生成映射关系文件,打包进main.js
  • cheap:只精确到行,不精确到列,打包速度快;只管业务代码,不管第三方模块
  • module:不仅管业务代码,而且管第三方代码
  • eval:执行效率最快,性能最好

同时,根据官网文档介绍,配置source-map方式打包后,项目打包速度会变慢,因为打包过程中会构建映射关系,比较耗费性能。因此,我们需要对开发环境和线上环境进行区分:

  • 开发者模式下(development):去使用sourceMap的话,建议使用cheap-module-eval-source-map的形式,这种方式提示错误比较全,同时打包速度也是比较快的。
  • 生产者模式下(production):使用cheap-module-source-map可以快速定位问题。

同时,sourceMap还可以通过nginx设置将.map文件只对白名单开放。 

13,什么是模热更新,如何配置模块热更新

模块热更新是webpack的一个功能,它可以使得代码修改之后,不用刷新浏览器就可以更新。在应用过程中替换添加删出模块,无需重新加载整个页面,是高级版的自动刷新浏览器。优点是只更新变更内容,以节省宝贵的开发时间。调整样式更加快速,几乎相当于在浏览器中更改样式。

开启Webpack热更新需要借助webpack.HotModuleReplacementPlugin(),devServer开启hot。比如,实现热更新,只更新指定的js模块。

if (module.hot) {  
    module.hot.accept(’./library.js’, function() {
      // Do something with the updated library module…  
    });
}

热更新的原理图下图所示。

image.png

14,什么是模块懒加载

懒加载或者按需加载,是一种很好的优化网页或应用的方式。实际上是先把代码在一些逻辑断点处分离开,然后在一些代码块中完成某些操作后,立即引用或引用另外一些新的代码块。这样加快了应用的初始加载速度,减轻了加载的体积。

在WebPack中,实现模块/文件的懒加载需要用到prefetch和preloading等关键字。比如,先加载主业务文件,然后利用网络空闲时间异步加载其他组件。

import(/* webpackPrefetch: true / ‘LoginModal’);

如果需要和主业务文件一起加载,异步加载组件,可以使用下面的方式。

import(/ webpackPreload: true */ ‘ChartingLibrary’);

15,什么是长缓存,在webpack中如何实现长缓存优化

Webpack打包的资源,最终都是在浏览器上呈现给用户,浏览器在用户访问页面的时候,为了加快加载速度,会对用户访问的静态资源进行存储。浏览器缓存可以分为强缓存和协商缓存两种。

强缓存是指在不访问服务器的情况下,直接从浏览器获取前端资源。读取资源的方式可以从缓存中读取,也可以从磁盘中读取。实现的方式是在响应头设置Expires字段或者Cache-Control字段。

协商缓存是指在访问服务器的情况下,服务器告知浏览器文件没变化,这时服务器会返回304,可以直接利用缓存中的资源。实现协商缓存的方式是两对组合(响应头/请求头):Last-Modified/If-Modified-Since ;ETag/If-None-Match。

对于长缓存,可以在output给出输出的文件制定chunkhash,并且分离经常更新的代码和框架代码,通过NameModulesPlugin或者HashedModulesPlugin使再次打包文件名不变。

参考:webpack4配置浏览器长缓存

16,polyfill和runtime有什么区别

babel-polyfill 的原理是当运行环境中并没有实现的一些方法,babel-polyfill会做兼容。babel-runtime 它是将es6编译成es5去执行。通常,我们使用es6的语法来编写应用,最终都会通过babel-runtime编译成es5。也就是说,不管浏览器是否支持ES6,只要是ES6的语法,它都会进行转码成ES5,所以就会产生很多冗余的代码。

babel-polyfill 它是通过向全局对象和内置对象的prototype上添加方法来实现的。比如运行环境中不支持Array.prototype.find 方法,引入polyfill, 我们就可以使用es6方法来编写了,但是缺点就是会造成全局空间污染。

babel-runtime它不会污染全局对象和内置对象的原型,比如说我们需要Promise,我们只需要import Promise from 'babel-runtime/core-js/promise’即可,这样不仅避免污染全局对象,而且可以减少不必要的代码。


xiangzhihong
5.9k 声望15.3k 粉丝

著有《React Native移动开发实战》1,2,3、《Kotlin入门与实战》《Weex跨平台开发实战》、《Flutter跨平台开发与实战》1,2和《Android应用开发实战》