文章目录
- webpack
- 概念
- 打包的场景
- 为什么要打包
- 在打包之外 - 翻译
- 在打包之外 - 小动作
- 课程重点
- 模块化
- 利用立即执行函数来改变 `作用域`
- 模块化的优点
- 模块化方案的进化史
- AMD(成型比较早,应用不是很广泛)
- COMMONJS
- ES6 MODULE
- webpack 的打包机制
- webpack 的打包过程
- 配置开发环境——npm 与包管理器
- package.json 配置参数的含义
- name
- version
- scripts (npm run xx)
- npm install 的过程
- npm install 命令的额外参数 -s、-d
- webpack 核心特性
- webpack 安装
- 指定入口文件和出口文件
- 处理 css、图片... 等资源
- 文件加载器-loader
- 插件-plugin
- webpack 构建工程
- 实战 demo(创了两个项目,用案例了解 loader 和 plugin 的作用原理)
- 为什么要用 babel
- 让 webpack 支持解析 ES6 语法以及 jsx 文件(react 模版文件)
- 让 webpack 支持解析 html
- webpack-dev-server 跑一个本地服务
- 热更新(不刷新页面的情况下把改动同步下来)
- webpack 性能调优
- webpack 是什么
- 阐述(个人觉得很重要,可以对技术发展有个新认识,比较能说服自己的)
- 扩展
webpack
概念
本质上,webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图**(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。
webpack 可以通过 loader 和 plugin 来扩展打包能力
- 例如 css-loader 来支持打包 css 文件…
- TerserPlugin 来做打包体积优化
打包的场景
为什么要打包
逻辑多、文件多,项目的复杂度提高了
在打包之外 - 翻译
他还能解释高级特性、语言(typescript / ES6 语法),这些代码直接在浏览器里写,可能浏览器无法识别,这时 webpack 就充当了翻译官的角色
在打包之外 - 小动作
提供了一些小插件
webpack 不仅强大,而且灵活(可插拔设计,需要的时候用,不需要的时候也可以移除掉,并没有绑定死)
课程重点
- 理解前端模块化
- 概念
- 不同模块化方案是如何实现的
- 理解 webpack 打包的核心思路
- 打包过程中都做了哪些步骤、做了哪些事、经历了哪些流程(后续遇到 bug 好排查)
- 理解 webpack 中的关键点
- plugin
- loader
入门的误区规避
- 不要执着于 API 和 命令
- API、命令,版本更新很频繁,很多 API 、命令可能下个版本就变更了
- 最主要的是了解他的概念、用来做什么的
模块化
作用域
- 按较早的写法,js 里的变量都是全局变量,会出现覆盖的问题,会导致变量重名、冲突的问题反复存在,会影响到你的预期结果
- 给同名变量不同的命名空间来解决这个问题
命名空间
模块化
利用立即执行函数来改变 作用域
保护对象的属性,不暴露出来,防止被随意篡改(模块作用域)
var SusanModule = (function() {var name = "Susan"var sex = "女孩"return {tell: function() {console.log("我的名字是", name)console.log("我的性别是", sex)}}
})()
较早期的模块实现方式(挂载到了 window 全局(浏览器的 window 对象是全局作用域))
(function(window) {var name = "Susan"var sex = "女孩"function tell() {console.log("我的名字是", name)console.log("我的性别是", sex)}window.SusanModule = { tell }
})(window)
模块化的优点
作用域封装
重用性
解除耦合(提升系统的可维护性,降低维护成本)
模块化方案的进化史
AMD(成型比较早,应用不是很广泛)
- Asynchronous Module Definition(异步模块定义)
- 显式的表达出了每个模块所依赖的其他模块有哪些,而且模块(内部变量?)的定义也不再绑定在全局作用域上,这更进一步地增强了模块的安全性,不必担心他在其他地方被篡改
// 求和模块的定义
define("getSum", ["math"], function (math) {return function (a, b) {console.log("sum:" + math.cum(a, b));}
});
COMMONJS
- 起初是服务端的标准,NODEJS 采用并实现了部分规范(2009)
- 每个文件就是一个模块,并且拥有它自己的一个作用域和上下文
- 模块的依赖通过 require 函数来引入,接口暴露通过 exports 导出
// 通过 require 函数来引入
const math = require("./math");// 通过 exports 将其导出
exports.getSum = function (a, b) {return a + b;
}
(上述两个方案的共同特性是规定模块的依赖必须显示引入,方便维护不同模块时,不必去操心引入顺序的问题)
ES6 MODULE
- 现在比较推荐的标准,语法级别的原生支持
// import 导入
import math from "./math";// export 导出
export function sum(a, b) {return a + b;
}
Gulp、GRUNT:帮助我们实现模块化构建的工具(比较老的工具库了)
- webpack 比他们后出现,更流行
webpack 的打包机制
-
webpack 与立即执行函数之间的关系
-
webpack 打包的核心逻辑
观察打包出来的结果,是一个立即执行函数
打包出来文件的大致结构:
(function(module) {var installedModules = {};function __webpack_require__(moduleId){// SOME CODE}// 。。。return __webpack_require__(0); // entry file})([ /* modules array */])
上述结构中的核心方法
function __webpack_require__(moduleId){// check if module is in cacheif(installedModules[moduleId]){return installedModules[moduleId].exports;}// create a new module (and put into cache)var module = installedModules[moduleId] = {i: moduleId,l: false,exports: {}};// exe the module funcmodules[moduleId].call{module.exports,module,module.exports,__webpack_require__};// flag the module as loadedmodule.l = true;// return the exxports of the modulereturn module.exports;}
webpack 的打包过程
- 从入口文件开始,分析整个应用的依赖树
- 将每个依赖模块包装起来,放在一个数组里面等待调用
- 实现模块加载的方法,并把它放在模块执行的环境中,确保模块间可以相互调用
- 把执行入口文件的逻辑放在一个函数表达式中,并立即执行这个函数
配置开发环境——npm 与包管理器
-
理解包管理器
-
熟悉 npm 核心特性
-
理解 npm
仓库
与依赖
的概念 -
理解 npm
语义化版本
-
掌握使用 npm 自定义工程脚本的方法
推荐将 npm 的仓库源设为国内的 淘宝源
- 在工作中发现很多文章博客都提出,不要使用 cnpm 工具,如果为了下载更快,推荐使用 淘宝源,而不是 cnpm
- 设置为 淘宝源 …
package.json 配置参数的含义
name
仓库名名称(npm install 名称、若要上传 npm,就要保证唯一)
version
语义化版本(npm install 的时候会自动选版本)
^version: 中版本和小版本
^1.0.1->1.x.x
~version: 小版本
~1.0.1->1.0.x
version: 特定版本
1.0.1->1.0.1
scripts (npm run xx)
可以定义一些命令(如启动服务、语法检查、webpack 打包等)
"dev": "webpack-dev-server"
"build": "eslint .... && webpack"
定义好的命令常见用法
- npm run build/dev/serve…
npm install 的过程
- 寻找包版本信息文件(package.json),依照它来进行安装
- 查 package.json 中的依赖,并检查项目中其他的版本信息文件
- 如果发现了新包,就更新版本信息文件
删掉重装有时解决不了问题,是这里面写的版本有问题(而不是依赖顺带安装的)
npm install 命令的额外参数 -s、-d
npm install echarts -s
- 安装 echarts ,并写入到 package.json 的 dependences 里,别人获取到项目时,npm install 就能安装相同的环境依赖,我们迁移代码的时候就可以不传 node_modules 文件夹,npm install 自动安装(npm 5.x 的版本在 npm install xx 的时候,把 -s 作为了默认参数)
- 故推荐使用 npm 5.x 以上的版本(新开发环境开始写代码前,也建议检查一下 npm 版本,可省事一些)
npm install echarts -d
- 安装 echarts,…放到了 devdependences 里,…
webpack 核心特性
- 使用 webpack 构建简单的工程
- 了解 webpack 配置文件
- 掌握 一切皆模块与loaders 的思想
- 理解 webpack 中的关键人物(关键部分)
- 入口文件、出口文件、loaders、plugin…
webpack 安装
npm install webpack-cli -g
全局安装,这样我们就可以直接在命令行里运行 webpack 命令了(一般在项目根目录执行 webpack 进行打包)- 去项目根目录的 src 下找 index.js 作为入口文件,分析依赖关系进行打包,然后生成 dist 文件夹,并将打包好的 main.js 放进去
- webpack 会有一些默认配置,如果我们需要自定义,一般会在项目根目录下新建一个 webpack.config.js 文件,里面写自定义配置(指定入口文件、出口文件、loaders、plugins 、web 服务的端口等)
指定入口文件和出口文件
// webpack.config.js
const path = require("path")module.exports = {entry: "./app.js",output: {path: path.join(__dirname, "dist"),filename: "bundle.js"}
}
webpack-dev-server
(命令行命令(需要安装全局 webpack-dev-server),不安装也可以,直接执行 ./node_modules/.bin/webpack-dev-server
)
- 可以帮我们启动一个 webpack 本地服务,监听项目文件的改动,当我们修改项目文件并保存的时候,它可以为我们动态的,实时的重新打包,并刷新浏览器(并不会在 dist 目录下生产文件(应该是在内存中))
处理 css、图片… 等资源
文件加载器-loader
(有 webpack 的项目中,可以在 js 文件里引入 图片、css、less、scss 等,webpack 会去处理这些依赖 => 需要引入对应的 loader ,并且没有意义)
npm install css-loader style-loader --save-dev
并在 webpack 中配置 loader- loader 的配置顺序和加载顺序是相反的,搞错了可能会报错(每个版本的 webpack 中的配置都会有一些不一样)
- 先写 style-loader 再写css-loader => 先解析 css,然后再用 css-loader 把 css 片段塞进去,所以 css-loader 要放后面
- 什么文件用什么 loader 解析(根据文件类型配置 loader)
- loader 的配置顺序和加载顺序是相反的,搞错了可能会报错(每个版本的 webpack 中的配置都会有一些不一样)
插件-plugin
做资源的处理、压缩(去除代码中不必要的内容,注释、换行…)
npm install uglifyjs-webpack-plugin -save-dev
并在 webpack 中配置 plugin(uglifyjs 是用来压缩代码体积的插件)
webpack 构建工程
- 使用 webpack 构建真实的 react 工程
- 掌握 babel 的用法,理解 babel 原理
- 掌握高频的 laoder 和 plugin 的用法
- 掌握生产级别的 webpack 配置方法
实战 demo(创了两个项目,用案例了解 loader 和 plugin 的作用原理)
并没有记全
新建项目文件,在根目录下 npm init
初始化一个项目(npm init -y
≈ 使用 npm 默认配置项,不需要一路回车设置项目名等)
npm install react react-dom
npm install webpack webpack-cli -g
- 将 webpack 安装到全局(类似?环境变量)
创建 src/App.jsx
…
为什么要用 babel
并不是所有的浏览器都支持 ES6 语法的,必须是版本比较高一些,比较新一些的浏览器才支持这个语法
- 如何把 ES6 的代码转换成版本较低的 JS 语法
- 可以使用 babel 来处理,进行代码的编译,把高版本的 JS 语法编译成低版本的 JS 语法
- 安装
npm install @babel/core @babel/cli -g
(在全局) - 新建一个 babel 项目,测试 babel 转换 JS 的 ES6 语法到 ES5 语法
npm install @babel/preset-env
这个包,可以把 高版本的 JS 语法转换成低版本的- 也支持像 webpack 一样的配置文件配置,可以直接写在 package.json 里(也可以在项目根路径创一个 .babelrc 文件写)
"babel": { "presets": ["@babel/preset-env"] }
- babel 命令在执行的时候如果没有指定规则,会优先查 .babelrc,没有就去看 package.json 里的配置
- 也支持像 webpack 一样的配置文件配置,可以直接写在 package.json 里(也可以在项目根路径创一个 .babelrc 文件写)
babel test.js --presets=@babel/preset-env
webpack 本身只处理ES5版本及以下的JS,ES6语法直接放在 webpack 下打包,肯定会报错的
html 在 webpack 中怎么处理
让 webpack 支持解析 ES6 语法以及 jsx 文件(react 模版文件)
npm install babel-loader
babel 的 loader
npm install @babel/preset-env @babel/preset-react
语法解析规则
安装完后在 webpack 中配置使用
让 webpack 支持解析 html
npm install html-webpack-plugin -d
并在 webpack 中配置
webpack 打包
webpack-dev-server 跑一个本地服务
(可以加 --config 指定配置文件,–open 直接使用浏览器打开…)
- 命令太长了每次打这么多很麻烦,我们也可以在 package.json 中写 scripts 脚本命令,然后用 npm run xx 来代替那些那么长的代码(写个 start/dev/serve /build)
热更新(不刷新页面的情况下把改动同步下来)
- HMR(webpack 已经支持了),在 webpack 配置中配置(入口文件里也要写点代码)
(webpack 5.x 和 html-webpack-plugin 不太兼容,有问题,所以按视频的操作跑不起来,需要指定版本)
webpack 性能调优
- 打包结果优化
- 用户体验
- 构建过程优化
- 开发者打包体验
- Tree-Shaking(比喻:摇树,把没用的树叶摇下来)
- webpack 中的特点,剔除一些无用的代码(DCE)
- 没有调用过的函数,直接就不引入进来
webpack --mode production
打包会自动给我们做代码压缩等操作,优化打包结果,当然我们也可以定制打包结果(压缩插件 uglifyjs => uglify-es => terser…)
- 使用 cache 缓存,可以加快我们的构建速度,一定时间内不会重复的去做压缩
- 移除无用代码、debugger、console、无用代码…
- 有时为了加快打包速度,也会加上 parallel 配置,开启多线程打包
webpack-bundle-analyzer 插件可以用来让我们可视化的看我们的打包结果,根据可视化可以看出哪些文件的体积最大/过大,然后做一个针对性的优化,有没有改动的余地
npm install webpack-bundle-analyzer
并在 webpack 中做配置
加快构建速度
- 先分析影响构建速度的因素有哪些
- 解析
- 减少需要打包的文件:比如 echarts、jquery,因为没有遵循模块化的标准,可能 webpack 解析起来很费时间,所以我们可以配置 webpack 不去解析他们(配置个 noParse …)、exclude 不解析 node_modules 下的文件 或者 include 只打包指定的打包文件
- 增加干活的人:开启多线程来打包,加快效率
- HappyPack 插件 / thread-loader loader,可以让 webpack 支持多个线程同时打包(如果简单的项目,可以不用,用不好可能更慢)
- 如果要使用 thread-loader,需要把它放在所有的 loader 之前(放在 rules 数组中的 use 数组的第一项?(是哪个数组的第一项?rules?use?))
- happypack 的 wiki(github wiki)中有一个列表,列出了它所支持的 loader(往里面加 loader 之前去看一下)
- HappyPack 插件 / thread-loader loader,可以让 webpack 支持多个线程同时打包(如果简单的项目,可以不用,用不好可能更慢)
- 提前干活:使用预编译
- 换 loader,fast-sass-loader 比 sass-loader 快
- 减少工作量:使用缓存(时效性不是很强)
- … 其他一堆的因素及对应的优化思路
- 解析
node 和 webpack 都是单进程的,所以一般优化都是在 线程的程度上
webpack 是什么
- 前端发展的产物
- 模块化打包方案
- 工程化方案
阐述(个人觉得很重要,可以对技术发展有个新认识,比较能说服自己的)
前端技术的蓬勃发展
-
我们想在 js 里面更方便地实现 html ,社区就有了 jsx
-
我们觉得原生的 css 不够好用,社区就给了我们 sass 和 less
-
针对前端模块化开发越来越强的需求,社区有了 AMD、COMMONJS、ES2015等等的方案
-
但这些方案大多并不被浏览器直接的支持,所以说往往伴随着这些新技术而生的还有另外一些让这些新技术应用于浏览器的方案
- 于是我们就用 babel 来转换下一代的 js,转换 jsx…
- 我们用各种各样的工具来转换 sass、less,转换为 css
- 我们发现项目越来越复杂,代码体积越来越大,又开始寻找各种优化、压缩、分割的方案
-
前端工程化的过程,真的是让我们开发者大费精力,人们也是纷纷在寻找前端模块化的过程中才知晓了 webpack,webpack 的流行确实是得益于野性发展的前端,但它的本质是一个前端模块化打包的一个解决方案,更重要的是它又是一个可以融合运用各种前端新技术的平台
扩展
-
学习的参考视频:【2020新课程】Webpack从原理到实战完整版-深入浅出,简单易学,前端工程师必学经典内容!
- 用来了解或者入门 webpack 还是不错的,感觉老师讲的挺好,深度要靠自己去扩展
- 视频中的实战 demo 需要注意 webpack 版本,个人尝试因版本问题未成功,不过了解个原理也挺好,满足了
-
别人写的笔记:webpack学习笔记:发展背景、工作机制、核心配置、性能优化
-
关于webpack项目中报错:Error: Cannot find module ‘webpack/lib/node/NodeTemplatePlugin’ 的解决办法
-
Webpack 5: The ‘compilation’ argument must be an instance of Compilation #1451
-
【webpack】HtmlWebpackPlugin插件报错,The ‘compilation‘ argument must be an instance of Compilation
-
webpack超详细配置, 使用教程(图文)(2017-05-06 的博客,有点老了,不知道价值怎么样)