webpack笔记(1)

总体流程

webpack在启动后会从entry里面配置的module开始,递归解析entry依赖的所有module.每找到一个Module,就会根据配置的loader去找对应的转换规则,
对module进行转换后,再解析出当前Module所依赖的Module,这些模块会以entry作为分组,一个entry及其所依赖的所有module会被分配到一个组也就是一个chunk中,
最后,webpack将这些chunk转换为文件输出。整个流程中,webpack会广播出各种事件,插件能够在适当时机执行plugin里面的逻辑。

核心概念

entry:入口
module:webpack中一切皆模块,一个文件对应一个模块,webpack会从配置的entry开始递归解析依赖的module
chunk:代码块,一个chunk可以由多个模块组成,用于代码的分割和合并。bundle是webpack打包出来的文件。
loader:模块转换器,用于将模块的原内容按照需求转换成新内容
plugin:扩展插件,在webpack的构建流程中的特定时机注入扩展的逻辑,来改变构建结果或做我们想做的事情
output:输出:在webpack经过一系列流程后,将最终的代码进行输出。

基本功能

代码转换:ts转换为js,scss编译成css等
文件优化:压缩js,css,html代码,压缩合并图片
代码分割:提取多个页面的公共代码,提取首屏不需要执行的代码并且让其异步加载
模块合并:在采用模块化开发的项目中,构建功能将模块分类合并成一个文件
自动刷新:热跟新,监听本地代码的变化,自动构建,刷新浏览器
代码校验:在代码提交到仓库前,检查代码是否符合规范(eslint),以及单元测试是否通过
自动发布:更新完代码后,自动构建上线发布。

webpack和gulp,grunt有什么区别

都是打包工具,grunt和gulp在早期更加流行,现在不大流行。
gulp和grunt都是基于任务或者流的思想,找到一个或者类似的文件,对其进行一系列的操作,更新流上的输出。
webpack是基于入口的,会自动递归的解析所需要加载的所有资源文件,用不同的loader进行处理,用plugin来扩展webpack功能。
gulp和grunt需要开发者将前端构建过程拆分成不同的任务,并且合理控制任务之间的流程,然而webpack只要开发者找到入口文件,并弄清楚各个文件用什么loader加载。

webpack简单实用

本项目安装webpack

1
npm i webpack --save-dev

配置webpack.config.js 配置webpack的6大概念

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const path = require('path');
module.exports = {
entry:'./main.js', //入口文件
output:{
filename:'bundle.js', //所有依赖的模块合并并且输出到一个bundle,js文件
path:path.resolve(__dirname,'./dist').//输出文件都放到dist目录下
},
module:{
rules:[
{
test:/\css$',
use:['style-loader','css-loader']
}
]
},
plugin:[
new ExtractTextPlugin({
filename:`[name]_[contentlength"8].css` //从js文件中提取出.css文件的名称 这样bundle.js下面就没有css代码了,生成新的css文件,再写入index.html中。
})
]
};

loader表示在遇到哪些文件时,用哪些loader去转换,从右到左的顺讯,首先实用css-loader,再使用style-loader转换器将css内容注入到js中。
plugin:是用来扩展webpack功能的,通过在构建流程中注入钩子函数实现,
常见的loader:
file-loader:把文件输出到一个文件夹当中,在代码中通过相对URL去引用输出的文件
image-loader:加载并且压缩图片文件
babel-loader:
css-loader:加载 CSS,支持模块化、压缩、文件导入等特性style-loader:把 CSS 代码注入到 JavaScript 中,通过 DOM 操作去加载 CSS。eslint-loader:通过 ESLint 检查 JavaScript 代码
常见的plugin:
define-plugin:定义环境变量
cmomon-chunk-plugin:提取公共代码
uglifyjs-webpack-plugin:通过UglifyES压缩ES6代码
loader和plugin的不同:作用不同:webpack原生只能解析js文件,loader扩展其解析非js文件的能力,plugin让webpack更加的灵活,
在webpack运行的生命周期中会广播出很多的事件,plugin可以监听这些事件,在合适的时机通过webpack提供的api改变输出的结果。
不同的用法:loader在module.rules中配置,是为了模块的解析规则而存在,类型为数组,plugin在plugins中单独配置,类型为数组,参数都是通过构造函数的方式传入

DevServer的使用

主要功能:
1.提供http服务而不是用本地文件预览
2.监听文件的变化并且自动刷新网页,做到实时预览
3.支持source map,方便调试
devServer会启动一个http服务器用于服务网页请求。同时帮助启动一个wbpack,并且接受webpack发出的文件变更信号,通过websocket协议自动刷新网页做到实时预览.
webpack在启动时会开启监听模式,之后webpack会监听本地文件系统的变化,在发生变化后重新构建出新的结果。默认是关闭的,开启 webpack –watch
注意:通过devServer开启的webpack自动开启监听模式.
当发生变化时会重新执行构建的程序,然后通知DevServer,devServer会让webpack在构建出的js代码中注入一个代理客户端用于控制网页,网页和devServer之间通过webSocket协议通信,
以方便devServer接收客户端发送的命令,devServer在收到来自webpack的文件变化通知后,通过注入的客户端控制网页刷新

模块热替换

除了重新刷新整个网页来实时预览,devServer还有一种被称为模块热替换的刷新技术,做到在不重新加载整个网页的情况下,通过将已跟新的模块替换老模块,再重新执行一次来实现实时预览。
相对于默认的刷新机制能提供更快的响应速度和更好的开发体验。默认是关闭的,要开启热替换,只需要在devServer时带上–hot即可。

支持sourceMap

浏览器中的js代码都是编译器输出的代码,这些代码的可读性非常差,在这样的代码中debug是非常差的体验,调试工具可以通过sourceMap映射代码,让我们在源代码的基础上调试。
只需要在启动时加上–devtool source-map参数即可。这样打开chrome的开发者工具,就可在source中看到可调式的源代码了。

webpack-dev-server和http服务器例如nginx有什么区别

webpack-dev-server使用内存来存储webpack开发环境下的打包文件,并且可以使用模块热更新,相比于传统的http服务器更加的高效。

热跟新究竟是怎么做到的,具体说明原理

1.在webpack的watch模式下,文件系统中某一个文件发生了变化,webpack就监听到了文件变化,根据配置文件对模块进行重新打包编译,并且将打包后的代码通过js代码保存在内存中
2.webpack-dev-server(在服务端的),webpack-dev-server和webpack之间的接口交互,主要是dev-server的中间件webpack-dev-middleware和webpack之间的交互,
webpack-dev-middleware调用webpack暴露的API对代码的变化进行监控,并且告诉webpack,将打包好的代码保存到内存中
3.webpack-dev-server对文件变化的控制,不同于第一步,并不是监控代码变化重新打包,当我们在配置文件中配置了devServer.watchContentBase为true的时候,
Server 会监听这些配置文件夹中静态文件的变化,变化后会通知浏览器端对应用进行 live reload。注意,这儿是浏览器刷新,和 HMR 是两个概念。
4.webpack-dev-server的工作,通过sockjs(webpack-dev-server)的依赖在浏览器端和服务器端建立一个websocket链接。将webpack编译打包的各个阶段状态信息告诉浏览器端,
浏览器根据这些socket进行不同的操作。当然服务端传递的最主要信息还是新模块的 hash 值,后面的步骤根据这一 hash 值来进行模块热替换。
5.webpack-dev-server/client并不能请求更新的代码,也不执行更新的操作,又把这些工作交回给了webpack,webpack/hot/-devserver工作是根据前者传过来的信息决定刷新浏览器还是进行模块热更新。
6.HotModuleReplacement.runtime是客户端HMR的中枢,接收上一步传递给他的新模块的hash值,它通过 JsonpMainTemplate.runtime 向 server 端发送 Ajax 请求,服务端返回一个 json,该 json 包含了所有要更新的模块的 hash 值,获取到更新列表后,该模块再次通过 jsonp 请求,获取到最新的模块代码。
7.hotModulePlugin会对比新旧模块,决定是否使用热更新。
8.最后一步,当 HMR 失败后,回退到 live reload 操作,也就是进行浏览器刷新来获取最新打包代码。

利用webpack优化前端性能

1.压缩代码,删除冗余代码,利用webpack的uglifyJSPlugin和ParallelUglifyPlugin来压缩代码。利用cssnano来压缩css
2.利用CDN加速。将引用的静态资源路径改为CDN上的相对路径。。可以利用webpack对于output参数和各loader的publicPath参数来修改资源路径
3.删除死代码,tree-Shaking,将代码中永远不会走到的片段删除掉。–optimize-minimize来实现
4.提取公共代码

如何提高webpack的构建速度?

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

alias说明

resolve配置webpack如何寻找模块所对应的的文件,webpack内置js模块化语法的解析功能,默认采用模块化标准里约定的规则去找,也可以根据自己的需要去修改默认的规则。
resolve.alias通过别名将原导入路径映射成一个新的导入路径。

1
2
3
4
5
resolve:{
alias:{
components:'./src/components/'
}
}

所以import Button from ‘components/button’导入时,被实际替换成了import Button from ‘./src/components/button’

配置单页和多页应用

单页应用是webpack的标准默哀是,直接在entry中指定单页应用的入口即可
多页应用:使用webpack的autoWebPlugin来完成简单自动化的攻坚,前提是项目的目录结构必须遵守他预设的规范。
多页应用的注意点:
1.每个页面的公共代码可以抽离出来,避免重复的加载。
业务的加载,页面的需求会不断的加载,所以一定要让入口的配置更加灵活,避免每次添加新页面的时候还需要修改构建配置。
webpack面试题1
webpack面试题2

webpack配置单页和多页的应用程序

单个页面

1
2
3
module.exports = {
entry:'./path/to/my/entry/file.js'
}

多页面应用程序

1
2
3
4
5
6
module.exports = {
entry:{
pageOne:'./src/pageOne/index.js',
pageTwo:'./src/pageTwo/index.js'
}
}

npm打包时注意的规范
利用webpack来上传npm包
npm模块需要注意以下几个问题
1.要支持commonjs模块化规范,所以要求打包后的最后结果也遵守该规范
2.npm模块最后编写的结果应该是es5标准的
3.npm大小应该是尽量小
4.发布的模块不能将依赖的模块一起打包,应该让用户自行选择去安装,可以避免模块应用这儿再次打包时出现底层模块重复打包的情况。
5.UI组件类的模块应该将依赖的其他资源文件,例如.css文件也需要包含在发布的模块中。
基于以上需要注意的问题,我们可以对于webpack配置做以下扩展和优化:
1CommonJS模块化规范的解决方案: 设置output.libraryTarget=’commonjs’使输出的代码符合CommonJS 模块化规范,以供给其它模块导入
2.使用输出ES5代码的解决方案:使用babel-loader把 ES6 代码转换成 ES5 的代码。再通过开启devtool: ‘source-map’输出SourceMap以发布调试。
3.Npm包大小尽量小的解决方案:Babel 在把 ES6 代码转换成 ES5 代码时会注入一些辅助函数,最终导致每个输出的文件中都包含这段辅助函数的代码,造成了代码的冗余。解决方法是修改.babelrc文件,为其加入transform-runtime插件
4.不能将依赖模块打包到NPM模块中的解决方案:使用externals配置项来告诉webpack哪些模块不需要打包。
5.对于依赖的资源文件打包的解决方案:通过css-loader和extract-text-webpack-plugin来实现,配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const ExtractTextPlugin = require('extract-text-webpack-plugin');

module.exports = {
module: {
rules: [
{
// 增加对 CSS 文件的支持
test: /\.css/,
// 提取出 Chunk 中的 CSS 代码到单独的文件中
use: ExtractTextPlugin.extract({
use: ['css-loader']
}),
},
]
},
plugins: [
new ExtractTextPlugin({
// 输出的 CSS 文件名称
filename: 'index.css',
}),
],
};

webpack流程概括

1.webpack的运行流程是一个串行的过程,从启动到结束会依次执行下面的流程

1.1初始化参数:从配置文件和shell语句中读取和合并参数,得出最终的参数,在这个过程当中还会执行配置文件中的插件的实例化语句(new Plugin())

1.2开始编译:用上一步得到的参数初始化compilier对象,Compilier负责文件的监听和启动编译,在compilier实例中包含了完整的webpack配置,全局只有一个compilier实例
加载插件,依次调用插件的apply方法,让插件可以监听后续的所有节点事件节点,同时会向插件中传入compilier实例的引用,以方便插件通过compilier调用webpack提供的API。
加载所有配置的插件,通过执行对象的run方法开始执行编译。

1.3确定入口:根据配置中的entry找出所有入口文件

1.4编译模块:从入口文件出发,调用所有配置的loader对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理。

1.5完成模块编译:在经过第4步使用loader翻译完成所有的模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系。

1.6输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的chunk,再将每个chunk转换成一个个文件加入到输出列表中,这是修改文件内容的最后机会.

1.7输出完成:在确定好输出内容之后,根据配置确定好的路径和文件名,将文件的内容输出。

注意:webpack会在确定的时间点广播特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用webpack提供的API改变webpack的运行结果

webpack大致流程

初始化:启动构建,读取和合并配置参数,加载plugin,实例化compiler.

编译:从entry出发,针对每个module串行调用对应的loader去编译文件的内容,再找到该module依赖的module,递归的进行编译处理。

输出:将编译后的module组装成chunk,将chunk再转换成文件,输出到对应的文件中。

初始化详细流程:

初始化参数:从配置文件和shell语句中读取与合并参数,得出最终的参数,在这个过程中还会进行插件实例化的语句new plugin()
实例化compilier:用上一步得到的参数初始化compilier实例,compilier负责文件的监听和启动编译,在compilier实例中包括了完整的webpack配置,全局只有一个compilier实例
加载插件:依次调用插件的apply方法,让插件可以监听后续的所有事件节点,同时向插件中传入compilier的引用,以方便插件通过compilier调用webpack的API。
environment:开始使用Nodejs风格的文件系统到compilier对象,以方便后续文件的搜寻和读取。
entry-option:读取配置的entry,为每个entry实例化一个entryPlugin,为后面的递归遍历创造条件
after-plugin:调用完所有内置的和配置的插件的apply方法。
after-resolvers:根据配置来初始化resolver,resolver负责在文件系统中寻找指定路径的文件

编译阶段

run:启动一次新的编译
watch-run:在监听模式下启动编译,这个事件中可以捕获哪些文件发生了变化从而产生一次新的编译。
compilier:告诉插件新的编译将要启动,同时会给插件新的compilier对象。
compliation:当webpack以开发模式运行的时候,每当检测到文件的变化时,便会有一次新的compilation被创建。
make:一个新的compliation创建完毕后,就会从entry开始读取文件,开始递归的解析。
after-compiler:一次细腻的compliation执行完成。
invalid:当遇到文件不存在的时候,文件的编译错误便会触发这个错误。

输出阶段

should-emit 所有需要输出的文件已经生成,询问插件有哪些文件需要输出,有哪些不需要输出。
emit:确定好要输出哪些文件后,执行文件的输出,可以在这里获取和修改输出的内容。
done:成功完成一次完整的编译和输出流程。
failed:如果在输出阶段的流程中遇到错误,就会跳到这个本步骤。