一:webpack核心功能
webpack安装和使用
推荐本地安装 / 基本安装与使用
mkdir webpack-demo && cd webpack-demonpm init -ynpm install webpack webpack-cli --save-dev
编译结果分析
编译的代码为什么放在 eval 中 
报错时,看不到其他代码的干扰,相当于一个新的环境。更加容易调试
配置文件
默认情况下,webpack会读取webpack.config.js 文件作为配置文件,但也可以通过cli参数 —config 来指定某个配置文件.
npx webpack --config test.js
配置文件中通过 CommonJS 模块导出一个对象,对象中的各种属性对应不同的webpack配置
//webpack.config.jsmodule.exports = {}
问题:webpack 的配置文件只能使用CommonJS 模块化?
答:在打包的过程中,是在node中进行的,参与运行的。
可以想像为 执行 var config = require(“./webpack.config.js”)这段代码
基本配置
1.mode : 编译模式 development(开发) production(生产)
默认值是 production
2.entry :入口文件
entry:"./src/main.js"
3.output: 出口文件
output:{filename:'bundle.js'}
devtool 配置
source map 源码地图
实际上是一个配置,配置中记录了原始代码,还记录了转换后的代码和原始代码的对应关系
注:不建议在生产环境使用,文件较大,且代码暴露
编译过程
入口和出口
1.模块化代码中,比如require(“./“),表示当前js文件所在的目录
2.在路径处理中,”./“表示node运行目录
__dirname : 所有情况下,都表示当前js文件所在的目录
var path = require("path")path.resolve(__dirname,"dist")
入口 entry
默认值是./src/index.js
通过 entry 进行配置,真正配置的是 chunk
出口 output
默认值是./dist/main.js
output 告诉webpack在哪里输出它所创建的bundle,以及如何命名这些文件。
const path = require('path');module.exports = {entry: './path/to/my/entry/file.js',output: {path: path.resolve(__dirname, 'dist'),filename: 'my-first-webpack.bundle.js'}};
多个入口的时候,就不可以使用静态规则了
规则
name : chunkname
hash : 总的资源hash,通常用于解决缓存问题
拓展:为什么要多页面打包
很多场景下,单页应用的开发模式并不适用。比如公司经常开发一些活动页:
[https://www.demo.com/activity/activity1.html](https://www.demo.com/activity/activity1.html)[https://www.demo.com/activity/activity2.html](https://www.demo.com/activity/activity2.html)[https://www.demo.com/activity/activity3.html](https://www.demo.com/activity/activity3.html)上述三个页面是完全不相干的活动页,页面之间并没有共享的数据。然而每个页面都使用了React框架,并且三个页面都使用了通用的弹框组件。在这种场景下,就需要使用webpack多页面打包的方案了:
- 保留了传统单页应用的开发模式:使用Vue,React等前端框架(当然也可以使用jQuery),支持模块化打包,你可以把每个页面看成是一个单独的单页应用
- 独立部署:每个页面相互独立,可以单独部署,解耦项目的复杂性,你甚至可以在不同的页面选择不同的技术栈
因此,我们可以把多页应用看成是乞丐版的前端微服务。
Loader
补充: 其对应的配置是 module
webpack 只能理解 JavaScript 和 JSON 文件,这是 webpack 开箱可用的自带能力。loader 让 webpack 能够去处理其他类型的文件,并将它们转换为有效 模块,以供应用程序使用,以及被添加到依赖图中。
loader 有两个属性
test属性,识别出哪些文件会被转换。use属性,定义出在进行转换时,应该使用哪个 loader。
webpack.config.js
const path = require('path');module.exports = {output: {filename: 'my-first-webpack.bundle.js'},module: {rules: [{ test: /\.txt$/, use: 'raw-loader' }]}};
module.exports = {mode: "development",module: {rules: [{test: /index\.js/, //正则表达式匹配模块路径use: [{loader: "./loaders/test-loader.js"} //每个加载器的使用是一个对象] //匹配之后,使用哪些加载器}], //模块匹配规则}}
拓:可不可以实现自己的loader?
loader 的使用规则是从后往前。
loader 中是否可以使用ES6 Module ?
不可以,是在打包过程中遇到的,使用的是node环境。
plugin
loader功能定位 是转换代码
plugin本质就是一个带有 apply 方法的对象
var plugin = {apply: function(compiler){}}
通常将该对象写成构造函数的模式
class MyPlugin{apply(compiler){//在这里注册事件,类似window.onload}}var plugin = new MyPlugin()
使用
webpack.config.jsvar MyPlugin = require("./plugins/MyPlugin");module.exports = {mode: "development",plugins: [new MyPlugin()],};
区分环境
使用不同的配置文件进行打包 
使用webpack自带的函数功能
module.exports = function (env) {console.log(env);if (env && env.prod) {return {mode: "production",devtool: "none",};} else {return {mode: "development",devtool: "source-map",};}};
"scripts": {"dev": "webpack","prod": "webpack --env.prod"},
其他细节配置
20分钟
二:常用扩展
清除输出目录
即 再次 打包的时候,删除之前的打包文件
安装使用
npm i --save-dev clean-webpack-plugin
const { CleanWebpackPlugin } = require('clean-webpack-plugin');module.exportsplugins: [new CleanWebpackPlugin(),],};
自动生成页面
html-webpack-plugin
理解:在打包后自动引入js文件,因为js文件可能名称是变化的,所以需要这个插件。
npm i --save-dev html-webpack-plugin
const HtmlWebpackPlugin = require('html-webpack-plugin')plugins:[new HtmlWebpackPlugin()]
如何实现
emit
利用 fs 模块生成一个页面文件
给文件内容的合适的位置添加一个script元素
元素的src路径引用打包后的js
根据页面的模板去生成
new HtmlWebpackPlugin({template: "./public/index.html", //模板配置文件chunks:"all" // 默认配置}),
复制静态资源
copy-webpack-plugin
把静态文件拷贝到指定目录,纯静态内容
npm i copy-webpack-plugin -D
开发服务器
开发阶段:往往我们希望把最终生成的代码部署到服务器上,来模拟真实环境
webpack-dev-server 库
npm i webpack-dev-server -D
这里使用需要注意版本问题
"webpack": "^4.41.5","webpack-cli": "^3.3.10","webpack-dev-server": "^3.11.0"
当执行 webpack-dev-server 命令原理:
1.内部执行webpack命令,传递命令参数
2.开启watch
3.注册hooks :类似于 plugin. webpack-dev-server 会向webpack中注册一些钩子函数
1.将资源列表(assets)保存起来
2.禁止webpack输出文件
4.用 express 开启一个服务器,监听某个端口,当请求到达后,根据请求的路径,给与相应的资源内容
配置
- port 端口
open 自动打开浏览器
devServer:{port: 8000,open:true,// index:"index.html"}
proxy 代理
代理服务器,前面的 “/api” 是一个正则匹配,后面则是替换的网址
proxy:{"/api":'http://open.duyiedu.com'}
注意:这样代理请求的结果中 host 依旧是本地的域名 localhost:8000
proxy: {"/api": {target: "http://open.duyiedu.com",changeOrigin: true, //更改请求头的host和origin},},
- stats 输出信息
stats:{modules:false}
普通文件处理
- file-loader 生成依赖文件到输出目录,然后将模块文件设置为:导出一个路径
- url-loader 将依赖的文件转换为: 导出一个base64格式的字符串
url-loader 在 小图片文件 的时候可以直接生成 base64 实现,减少网络请求npm install file-loader --save-dev
解决路径问题
三:css工程化
css工程化概述
css的问题与如何解决?
类名冲突的问题
- 命名约定
- bem
- css in js
- RN中使用较多
- css model
- 命名约定
重复样式
网站的主色调,背景 文字 边框
- css in js
- 预编译器
- less
- sass
- css文件细分问题
利用 webpack 拆分css
css-loader
注:要在页面中进行使用才能发现 css-loader 的用处 ,下面这张是没有试用css代码,直接打包,并无报错。
如果在css代码中增加复杂处理,如图片
.red{background:url("./bg.png")}经过css-loader 转换后变成js代码var import1 = require("./bg.png")module.exports = `.red{background:url("${import1}")}`
注意
var res = require("./assets/banner.css");var css = res.default.toString(); //注意这里的数据结构var style = document.createElement("style");style.innerHTML = css;document.head.appendChild(style);
总结:css-loader 做了什么
- 将css文件的内容作为字符串导出
- 将css中的其他依赖作为require导入,以便webpack分析依赖
style-loader**
style-loader可以将css-loader转换后的代码进一步处理,将css-loader导出的字符串加入到页面的style元素中
BEM
Block Element Modifier
- Block 页面中的大区域,表示最顶级的划分
- element 区域中的组成部分
- modifier 可选,通常表示状态
css in js
css module
预编译器less
解决重复样式的问题
四:js兼容性
babel 的安装和使用
补充:@babel/polyfill 已过时,目前被core-js 与 generator-runtime 所取代
安装
- @babel/core babel 核心库,提供了编译所需的所有api
- @babel/cli 提供一个命令行工具,调用核心库的api完成编译
npm i -D @babel/core @babel/cli
使用
- 按文件编译
babel 要编译的文件 -o 编译结果文件
- 按目录编译
babel 要编译的整个目录 -d 编译结果文件
npx babel js/a.js -o js/b.js
配置
{"presets": ["@babel/preset-env"]}
兼容的浏览器
@babel/preset-env 需要根据兼容的浏览器范围来确定如何编译,如postcss一样,可以使用文件 .browserslistrc 来描述浏览器的兼容范围
last 3 version> 1%not ie <= 8
自身的配置
{"presets": [["@babel/preset-env",{"配置项1":"配置值"}]]}
比较常见的配置项是 usebuiltins ,默认值是false
由于该预设仅转换新的语法,并不对新的api进行任何处理
babel 插件
@babel/plugin-proposal-optional-chaining
安全性的读取
const obj = {foo:{bar:{baz:42}}}const baz = obj?.foo?.bar?.baz //42
babel-plugin-transform-remove-console
该插件会移除源码中的控制台输出语句
@babel/plugin-transform-runtime
用于提供一些公共的API 这些api会帮助代码转换
在webpack中使用babel
npm i -D @babel/core babel-loader
module.exports = {mode: "development",devtool: 'source-map',module: {rules: [{test: /\.js$/,use: "babel-loader"}]}}
拓展课程 未学习
五:性能优化
性能优化概述
不要过早的去关注性能
构建性能
指的是开发阶段的构建性能。优化的目标,是降低从打包开始,到代码效果呈现所经过的时间
传输性能
指的是js代码传输到浏览器经过的时间
1.总传输量
2.文件数量 即js文件数量
3.浏览器缓存
运行性能
取决于如何书写高性能的代码
减少模块解析
模块解析 :抽象语法树分析 依赖分析 模块语法替换
哪些模块不需要解析
模块中无其它依赖,一些已经打包好的第三方库,比如 jquery
配置 module.noParse 正则表达式
module.exports ={mode:"development",module:{noParse:/jquery/}}
对比打包时间 

优化loader性能
- 限制loader 的应用范围
**
对于某些库,不使用 loader
例如,babel-loader 可以转换ES6或更高版本的语法,可是有些库本身就是用ES5语法写的,不需要转换,使用babel-loader 反而浪费构建时间 loadsh
module.rule.exclude module.rule.include
module.exports = {module:{rules:[{test:/.\js$/,exclude:/lodash/,use:"babel-loader"}]}}
- 缓存loader的结果
如果某个文件内容不变,经过相同的loader解析后,解析后的结果也不变。
cache-loader 放到最前面却能够决定后续的loader是否运行。
实际上,loader运行的过程中,还包含一个过程,即 pitch
module.exports = {mode:'development',devtool:'source-map',module:{rules: [{test: /.\js$/,use: [{loader: "cache-loader",options: {cacheDirectory: './cache'}}, "babel-loader"]}]}}
- 为 loader 的运行开启多线程
thread-loader 会开启一个线程池,线程池中包含适量的线程
它会把后续的loader 放到线程池的线程中运行,以提高构建效率
由于后续的loader会放到新的线程中,所以,后续的loader不能
- 使用webpack api 生成文件
- 无法使用定义的 plugin api
- 无法访问 webpack options
热替换 HMR
与 webpack-dev-server 的区别
dev-server: 代码变动 => 浏览器刷新 重新请求所有资源
热替换 : 代码变动 => 浏览器仅请求改动的资源
const HtmlWebpackPlugin = require('html-webpack-plugin')module.exports = {mode:"development",devtool: "source-map",devServer:{open:true,hot:true},plugins:[new HtmlWebpackPlugin({template:"./public/index.html"})]}
import a from "./a";console.log(a)if(module.hot){//是否开启了热更新module.hot.accept() //接受热更新}
当开启了热更新后,webpack-dev-server 会向打包结果中注入 module.hot 属性
默认情况下,webpack-dev-server 不管是否开启了热更新,当重新打包后,都会调用 location.reload刷新页面,但是如果运行了 module.hot.accept() ,将改变这一行为module.hot.accept() 的作用是让 webpack-dev-server 通过 socket 管道,把服务器更新的内容发送到浏览器,然后将结果交给插件 HotModuleReplacementPlugin 注入的代码执行。
热替换发生在代码运行期
替换过程 
手动分包 (传输性能)
dll : dynamic link library 动态链接库
原理:
1.先单独的打包公共模块
单独配置 webpack.dll.config.js 文件进行打包
const webpack = require("webpack");const path = require("path")module.exports = {mode:'production',entry:{jquery:['jquery'],lodash:['lodash']},output:{filename:'dll/[name].js',library:"[name]" //每个bundle暴露的全局变量名},plugins:[new webpack.DllPlugin({path:path.resolve(__dirname,"dll","[name].manifest.json"),name:"[name]"})]}
webpack.DllPlugin webpack 自带的工具
自动分包
代码压缩
使用什么压缩工具?
目前最流行的代码压缩工具主要有两个, UglifyJS 和 Terser
UglifyJs 是一个传统的代码压缩工具,但是不支持 ES6 语法
Terser 是一个新的代码压缩工具。支持 ES6+ 语法。 webpack安装后会内置 Terser
关于副作用 (side effect)
副作用:函数运行过程中,可能会对外部环境造成影响的功能
如果函数中包含以下代码,该函数叫做副作用函数
1.异步代码
2.localStorage
3.对外部数据的修改
如果一个函数没有副作用,同时,函数的返回结果仅依赖参数,则该函数叫做纯函数(pure function)
tree shaking
场景:某些模块导出的代码并不一定会被用到
使用:webpack2 开始就支持了 tree shaking
只要是生产环境 tree shaking 自动开启
我们在编写代码的时候,尽量
- 使用
export default导出 而不使用export default {xxx}导出 - 使用
import {xxx} from 'xxx'而不使用import xxx from 'xxx'
使用第三方库
lodash使用的是 commonjs 的方式导出,对于这些库,tree shaking 无法发挥作用。lodash-es
两次打包对比

作用域分析 (不推荐)
tree-shaking 本身并没有完善的作用域分析,可能导致一些 dead code 函数中的依赖仍然被视为依赖webpack-deep-scope-plugin 提供了作用域分析,可解决这些问题
css tree shakingpurgecss-webpack-plugin
懒加载
**
Eslint(其它优化)
代码风格检查
安装使用npm i -D eslint

配置项
env
配置代码的运行环境
- browser : 代码是否在浏览器环境中运行
- es6 : 是否启用es6的全局api 例如 Promise
parseroptions
该配置指定 eslint 对哪些语法的支持
- ecmaVersion: 支持的ES语法版本
- sourceType
- script 传统脚本
- module 模块化脚本
rules
查阅文档
bundle analyzer
bundle分析webpack-bundle-analyzer
gzip
使用 compression-webpack-plugin 插件对打包结果进行预压缩,可以移除服务器的压缩时间
