随着Node.js的不断发展,前端项目的不断提升,我们的项目打包难免会出现庞大,打包慢的情况。趁着工作之外的时间,通读了关于webpack的数据,也在此总结一下相关的知识。
关注公众号《前端小时》回复webpack获取高清思维导图
目录
- 入门
- 配置
- 优化
- 原理
入门
前端发展
模块化
commonjs
commonjs的核心思想就是通过require的方式同步
引入依赖包,它是一种JavaScript规范
。
commonjs也分为两个版本,一个是commonjs1,只能用exports.XX = XX
的方式进行模块的导出;而commonjs2新添加了module.exports = XX
的方式导出。
优点
:
- 代码可复用
- 能够在`node环境运行
- 第三方模块都使用这种方式
缺点
:
- 无法直接运行在浏览器环境
- ES6代码需要转成ES5
AMD
AMD模块化方式也是一种JavaScript规范,它采用异步加载依赖,主要解决浏览器环境模块化问题。
优点
:
- 不转换代码直接运行在浏览器
- 异步加载
- 并行加载多个依赖
- 运行在浏览器和Node环境
缺点
:
- JavaScript运行环境没有原生支持AMD
- 需先引入AMD库才能使用
ES6模块化
它是异种骨ECMA的JavaScript模块化规范,支持浏览器和Node环境,但是缺点就是需要ES6转成ES5。
样式文件模块化(mixin)
还有一种是样式文件的模块化,现在CSS也可以像其他变成语言一样可以有编程的思想在里面。可以定义变量,公共样式的引入等。
新框架
- Vue
- React
- Angular
新语言
ES6
- 模块化
- Class语法
- let声明
- 箭头函数
- async函数
- Set和Map结构
TypeScript
TypeScript是JavaScript的一种超集,提静态类型检查
,避免我们在项目里面出现一些低级错误,一般大型项目都建议引入。但是很多不喜欢就是因为代码啰嗦
,它也无法在浏览器/Node环境运行
,需要转换。
Flow
- JavaScript的超集
- 提供静态类型检查
SCSS 一种css预处理器,常见的还有less,stylus等。
优点
- 方便管理代码
- 抽离公共部分
- 逻辑灵活
缺点
- 代码需要转化
常见构建工具
- npm script
- grunt
- gulp
- fis3
- webpack
- rollup
webpack的优势
- 专注模块化项目
- Plugin扩展
- 场景不限于web开发
- 社区庞大
- 开发体验良好,但只能开发模块化项目
核心概念
Entry
:第一步将从Entry开始,抽象输入Module
:一切皆模块,从Entry开始递归遍历所有依赖的模块Chunk
:代码块,一个Chunk由多个模块组成,用于代码分割与合并Loader
:模块转化器,将原内容转成新内容Plugin
:扩展插件,监听广播事件,回调
构建流程
代码转换
文件优化
代码分割
模块合并
自动刷新
代码校验
自动发布
webpack实时预览原理
webpack向JavaScript代码注入一个代理客户端,代理客户端用于控制网页的刷新。网页和devServer之间使用的是websocket协议,当webpack监听到代码改变时就会让webpack通知客户端进行页面的刷新。
配置
Entry
- context:寻找相对目录会以context为跟目录
- Entry类型:
string
array
object
- chunk名称:类型是string/array ,是main、如是object ,是object中的键名
- 动态配置Entry:设置成函数动态返回
Output
- filename:配置输出文件的名称,多个chunk可设置成 filename : [name].js
- chunkFilename:无入口的chunk在输出时的文件名,用于指定在运行过程中生成的chunk
- path:输出文件存放的本地目录
- publicPath:配置发布到线上的资源URL前缀
- crossOriginLoading:配置代码块异步加载的方式(JSONP),anonymous- 不会带上cookie,use-credentials-带上cookie
- libraryTarget:配置以何种方式导出库,
var
commonjs
commonjs2
this
window
global
- library:配置导出库的名称
- libraryExport:配置导出的模块中哪些子模块需要导出
Module
- noParse:忽略部分没采用模块化文件的递归解析
- parser:更细粒度配置哪些模块语法被解析
Resolve
- alias:通过别名映射新路径
- mainFields:配置采用哪一份代码,数组['brower' , 'main']
- extensions:配置不带后缀时寻找文件的方式,['.ts' , '.js' , '.json']
- modules:配置去哪些目录下寻找第三方模块,默认:node_modules
- descriptionFiles:配置第三方描述文件的名称,默认package.json,descriptionFiles: [ 'package.json' ]
- ecforceExtension:配置导入语句是否强制带后缀
- enforceModuleExtension:【node_modules】配置导入语句是否强制带后缀
DevServer
- hot:在不刷新整个页面的情况下通过新模块来替换旧模块,实现实时预览
- inline:配置是否将代理客户端注入到代码块里面来控制网页,默认true
- historyApiFallback:配置命中路由时返回的html文件
- contentBase:配置DevServer HTTP服务器的文件根目录,只能用来配置暴露本地文件的规则
- headers:可以在HTTP响应时注入一些HTTP响应头
- host:配置DevServer服务的监听地址
- port:服务监听的端口
- allowedHost:配置白名单列表,只有HTTP请求的host在列表才能正确返回
- disableHostCheck:是否关闭用于DNS重新绑定的HTTP请求的HOST检查,DevServer默认只接受来自本地的请求
- https:DevServer默认使用HTTP方式,是否切换成HTTPS服务
- clientLogLevel:配置客户端日志等级
- compress:是否开启Gzip压缩
- open:是否默认打开浏览器
其他配置
- Target:构建针对不同环境的代码
- DevTool:配置如何生成Source Map
- Watch/WatchOprions:Watch,文件监听;WatchOprions:配置哪些文件需要监听,变化后多久才会变化
- Externals:webpack打包忽略的模块
- ResolveLoader:告诉webpack如何寻找Loader
优化
缩小文件搜索范围
,使用DllPlugin
,使用HappyPack
,使用ParallelUglifyPlugin
,使用自动刷新
,开启模块热替换
,区分环境
,代码压缩
,CDN加速
,使用Tree Shaking
,提取公共代码
,分割代码以按需加载
,使用Prepack
,开启Scope Hoisting
,输出分析
缩小文件搜索范围
优化Loader配置
- 尽可能少文件被Loader处理
- 尽可能让Loader快速命中需处理的文件
优化resolve.modules配置
- 指明第三方文件的存放位置,减少搜索步骤
优化resolve.mainFields配置
- 指明当前环境所需的代码,减少搜索步骤
优化resolve.alias配置
- 尽可能明确别名路径
- 整体性较强使用此优化
- loadash不适合使用,会输出很多无用代码
优化resolve.extensions配置
- 后缀尝试列表尽可能少
- 频率出现高的文件后缀放前面
- 导入语句尽可能带上后缀
优化module.noParse配置
- 忽略部分没有采用模块化的文件递归解析处理
使用DllPlugin
- 将网页依赖的基础模块抽离出来,打包到一个个单独的动态链接库中
- 导入模块存在于某个动态链接库,模块不会再次打包,从库里取
- 页面依赖的所有动态链接库都需要被加载
使用HappyPack
定义
- 将任务分解多个子进程并发执行
- 子进程执行完将结果发给主进程
原理
- 实例化一个HappyPack,告诉核心调度器如何通过Loader转化一类文件
- 核心调度器会把任务分给子进程,结果发给主进程
使用ParallelUglifyPlugin
- 多进程并行代码压缩
使用自动刷新
文件监听
原理
- 定时获取文件的最后最后修改时间
- 当前获取和最后一次的编辑时间不一致则文件发生变化
- watchOptions中的poll属性可以设置每秒轮询多少次
- watchOptions中的aggregateTimeout属性可以设置收集时间,然后告诉监听者
优化
- 默认从Entry递归遍历,加到监听列表里面
- 忽略第三方模块的文件监听
- 加大收集时间,减少一秒轮询次数
自动刷新浏览器
方法
- 借助浏览器提供的接口刷新
- 向开发网页注入代理客户端
- 网页装进一个iframe中,刷新iframe
优化
- 关闭inline注入模式
- 通过执行命令来完成注入一个代理客户端
开启模块热替换
原理
- 源码发生变化后只编译发生变化的模块
- 用新模块去替换旧模块
优势
- 预览反应快,等待时间短
- 不刷新浏览器,保留网页运行状态
优化
- 使用内置的NamedModulesPlugin输出具体文件
- 监听更少的文件和忽略node_modules下面的文件
- 不可关闭inline注入,每个chunk都需要包含客户端代码
区分环境
- 线上环境代码压缩
- 测试环境专注开发
- 通过process.env.NODE_ENV
代码压缩
压缩JavaScript
- UglifyJsPlugin
- ParallelUglifyPlugin 多进程处理压缩
压缩ES6
- 需要单独安装uglify-webpack-plugin
压缩CSS
- cssnano,css-loader内置
CDN加速
- 针对html文件不开启缓存
- 文件名使用hash值
- 把不同类型的文件放到不同的CDN服务器上减少并发请求时间
- 可能会增加域名解析时间,可开启DNS预解析
使用Tree Shaking
- 剔除无用代码
- 必须使用ES6语法
- 第三方库可能不生效,原因是使用commonJS规范
提取公共代码
- 资源重复加载,浪费流量/服务器成本
- 首屏加载慢,影响用户体验
分割代码以按需加载
使用Prepack
- 编译代码时提前将结果放进编译后的代码中
- Babel将JavaScript源码抽象成语法树AST
- 内置js解释器,执行源码
- 不能识别DOM API和部分NodeJS API
- 优化后性能可能更差
- 优化后文件尺寸增加
开启Scope Hoisting
作用域提升
- 代码体积小
- 创建函数作用域变少,内存开销小
- 必须使用ES6模块化语句
使用
- webpack内置
- 第三方模块会使用commomJs规范,需要转换
- 开启降级处理(--display-optimization-bailout)
输出分析
- webpack-bundle-analyzer
原理
工作原理概括
基本概念
- Entry:第一步将从Entry开始,抽象输入
- Module:一切皆模块,从Entry开始递归遍历所有依赖的模块
- Chunk:代码块,一个Chunk由多个模块组成,用于代码分割与合并
- Loader:模块转化器,将原内容转成新内容
- Plugin:扩展插件,监听广播事件,回调
流程概括
- 开始编译:用参数初始化Compiler对象,加载所有配置的插件,执行run方法
- 确定入口:根据Entry找出所有的入口文件
- 编译模块:Loader递归对模块进行翻译
- 完成编译:得到模块之前的依赖关系以及内容
- 输出资源:根据关系,输出Chunk,再将每个Chunk转成单独文件输出到文件列表中
- 输出完成:根据配置输出文件路径和文件名,写到文件系统中
流程细节
- 初始化:启动构建,读取与配置参数,加载Plugin,实例化Compiler
- 编译:从Entry出发,递归遍历文件使用Loader处理
- 输出:将每个Chunk输出单个文件到文件系统中
Loader职责
- 职责单一,一次只能完成一次转换
- 多种转换需要用多个Loader顺序处理
加载本地Loader
- Npm link:在package.json配置,npm link注册到全局,npm link loader-name 连接到node_modules
- ResolveLoader:到哪里去寻找Loader,默认node_modules,可配置自己的Loader目录
Plugin编写
定义
- webpack在生命周期广播事件,plugin监听这些事件
原理
- webpack启动会生成BasicPlugin实例子
- 传入compiler对象
- compiler.plugin监听广播的事件
compiler/compilation
- Plugin和webpack之间的桥梁
- 前者包含了webpack环境的所有配置信息
- 后者包含当前的模块资源,编译生成的资源,变化的文件等
- Compiler代表webpack的整个生命周期
- Compilation只代表一次编译