https://www.webpackjs.com/

一:webpack核心功能

webpack安装和使用

推荐本地安装 / 基本安装与使用

  1. mkdir webpack-demo && cd webpack-demo
  2. npm init -y
  3. npm install webpack webpack-cli --save-dev

编译结果分析

编译的代码为什么放在 eval 中
image.png
报错时,看不到其他代码的干扰,相当于一个新的环境。更加容易调试

配置文件

默认情况下,webpack会读取webpack.config.js 文件作为配置文件,但也可以通过cli参数 —config 来指定某个配置文件.

  1. npx webpack --config test.js

配置文件中通过 CommonJS 模块导出一个对象,对象中的各种属性对应不同的webpack配置

  1. //webpack.config.js
  2. module.exports = {}

问题:webpack 的配置文件只能使用CommonJS 模块化?
答:在打包的过程中,是在node中进行的,参与运行的。
可以想像为 执行 var config = require(“./webpack.config.js”)这段代码


基本配置

1.mode : 编译模式 development(开发) production(生产)

默认值是 production

2.entry :入口文件

  1. entry:"./src/main.js"

3.output: 出口文件

  1. output:{
  2. filename:'bundle.js'
  3. }

devtool 配置

source map 源码地图
实际上是一个配置,配置中记录了原始代码,还记录了转换后的代码和原始代码的对应关系
注:不建议在生产环境使用,文件较大,且代码暴露

编译过程

入口和出口

1.模块化代码中,比如require(“./“),表示当前js文件所在的目录
2.在路径处理中,”./“表示node运行目录

__dirname : 所有情况下,都表示当前js文件所在的目录

  1. var path = require("path")
  2. path.resolve(__dirname,"dist")

入口 entry
默认值是./src/index.js
通过 entry 进行配置,真正配置的是 chunk

出口 output
默认值是./dist/main.js
output 告诉webpack在哪里输出它所创建的bundle,以及如何命名这些文件。

  1. const path = require('path');
  2. module.exports = {
  3. entry: './path/to/my/entry/file.js',
  4. output: {
  5. path: path.resolve(__dirname, 'dist'),
  6. filename: 'my-first-webpack.bundle.js'
  7. }
  8. };

多个入口的时候,就不可以使用静态规则了

规则
name : chunkname
hash : 总的资源hash,通常用于解决缓存问题
image.png

拓展:为什么要多页面打包

很多场景下,单页应用的开发模式并不适用。比如公司经常开发一些活动页: [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多页面打包的方案了:

  1. 保留了传统单页应用的开发模式:使用Vue,React等前端框架(当然也可以使用jQuery),支持模块化打包,你可以把每个页面看成是一个单独的单页应用
  2. 独立部署:每个页面相互独立,可以单独部署,解耦项目的复杂性,你甚至可以在不同的页面选择不同的技术栈

因此,我们可以把多页应用看成是乞丐版的前端微服务


Loader

补充: 其对应的配置是 module

webpack 只能理解 JavaScript 和 JSON 文件,这是 webpack 开箱可用的自带能力。loader 让 webpack 能够去处理其他类型的文件,并将它们转换为有效 模块,以供应用程序使用,以及被添加到依赖图中。

loader 有两个属性

  1. test 属性,识别出哪些文件会被转换。
  2. use 属性,定义出在进行转换时,应该使用哪个 loader。

webpack.config.js

  1. const path = require('path');
  2. module.exports = {
  3. output: {
  4. filename: 'my-first-webpack.bundle.js'
  5. },
  6. module: {
  7. rules: [
  8. { test: /\.txt$/, use: 'raw-loader' }
  9. ]
  10. }
  11. };

  1. module.exports = {
  2. mode: "development",
  3. module: {
  4. rules: [{
  5. test: /index\.js/, //正则表达式匹配模块路径
  6. use: [{
  7. loader: "./loaders/test-loader.js"
  8. } //每个加载器的使用是一个对象
  9. ] //匹配之后,使用哪些加载器
  10. }], //模块匹配规则
  11. }
  12. }

拓:可不可以实现自己的loader?

loader 的使用规则是从后往前。

loader 中是否可以使用ES6 Module ?
不可以,是在打包过程中遇到的,使用的是node环境。

plugin

loader功能定位 是转换代码

plugin本质就是一个带有 apply 方法的对象

  1. var plugin = {
  2. apply: function(compiler){
  3. }
  4. }

通常将该对象写成构造函数的模式

  1. class MyPlugin{
  2. apply(compiler){//在这里注册事件,类似window.onload
  3. }
  4. }
  5. var plugin = new MyPlugin()

使用

  1. webpack.config.js
  2. var MyPlugin = require("./plugins/MyPlugin");
  3. module.exports = {
  4. mode: "development",
  5. plugins: [new MyPlugin()],
  6. };

区分环境

使用不同的配置文件进行打包
image.png

使用webpack自带的函数功能

  1. module.exports = function (env) {
  2. console.log(env);
  3. if (env && env.prod) {
  4. return {
  5. mode: "production",
  6. devtool: "none",
  7. };
  8. } else {
  9. return {
  10. mode: "development",
  11. devtool: "source-map",
  12. };
  13. }
  14. };
  1. "scripts": {
  2. "dev": "webpack",
  3. "prod": "webpack --env.prod"
  4. },

其他细节配置

20分钟

二:常用扩展

清除输出目录

即 再次 打包的时候,删除之前的打包文件

安装使用

  1. npm i --save-dev clean-webpack-plugin
  1. const { CleanWebpackPlugin } = require('clean-webpack-plugin');
  2. module.exports
  3. plugins: [
  4. new CleanWebpackPlugin(),
  5. ],
  6. };

自动生成页面

html-webpack-plugin

理解:在打包后自动引入js文件,因为js文件可能名称是变化的,所以需要这个插件。

  1. npm i --save-dev html-webpack-plugin
  1. const HtmlWebpackPlugin = require('html-webpack-plugin')
  2. plugins:[
  3. new HtmlWebpackPlugin()
  4. ]

如何实现
emit
利用 fs 模块生成一个页面文件
给文件内容的合适的位置添加一个script元素
元素的src路径引用打包后的js

根据页面的模板去生成

  1. new HtmlWebpackPlugin({
  2. template: "./public/index.html", //模板配置文件
  3. chunks:"all" // 默认配置
  4. }),

复制静态资源

copy-webpack-plugin

把静态文件拷贝到指定目录,纯静态内容

  1. npm i copy-webpack-plugin -D

开发服务器

开发阶段:往往我们希望把最终生成的代码部署到服务器上,来模拟真实环境
webpack-dev-server 库

  1. npm i webpack-dev-server -D

这里使用需要注意版本问题

  1. "webpack": "^4.41.5",
  2. "webpack-cli": "^3.3.10",
  3. "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 自动打开浏览器

    1. devServer:{
    2. port: 8000,
    3. open:true,
    4. // index:"index.html"
    5. }
  • proxy 代理

代理服务器,前面的 “/api” 是一个正则匹配,后面则是替换的网址

  1. proxy:{
  2. "/api":'http://open.duyiedu.com'
  3. }

注意:这样代理请求的结果中 host 依旧是本地的域名 localhost:8000

  1. proxy: {
  2. "/api": {
  3. target: "http://open.duyiedu.com",
  4. changeOrigin: true, //更改请求头的host和origin
  5. },
  6. },
  • stats 输出信息
    1. stats:{
    2. modules:false
    3. }

普通文件处理

  • file-loader 生成依赖文件到输出目录,然后将模块文件设置为:导出一个路径
  • url-loader 将依赖的文件转换为: 导出一个base64格式的字符串
    1. npm install file-loader --save-dev
    url-loader 在 小图片文件 的时候可以直接生成 base64 实现,减少网络请求

解决路径问题

三:css工程化

css工程化概述

css的问题与如何解决?

  • 类名冲突的问题

    • 命名约定
      • bem
    • css in js
      • RN中使用较多
    • css model
  • 重复样式

网站的主色调,背景 文字 边框

  • css in js
  • 预编译器
    • less
    • sass
  • css文件细分问题

利用 webpack 拆分css

css-loader
注:要在页面中进行使用才能发现 css-loader 的用处 ,下面这张是没有试用css代码,直接打包,并无报错。
image.png

如果在css代码中增加复杂处理,如图片

  1. .red{
  2. background:url("./bg.png")
  3. }
  4. 经过css-loader 转换后变成js代码
  5. var import1 = require("./bg.png")
  6. module.exports = `.red{
  7. background:url("${import1}")
  8. }`

注意

  1. var res = require("./assets/banner.css");
  2. var css = res.default.toString(); //注意这里的数据结构
  3. var style = document.createElement("style");
  4. style.innerHTML = css;
  5. document.head.appendChild(style);

总结:css-loader 做了什么

  1. 将css文件的内容作为字符串导出
  2. 将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完成编译
    1. npm i -D @babel/core @babel/cli

使用

  • 按文件编译

babel 要编译的文件 -o 编译结果文件

  • 按目录编译

babel 要编译的整个目录 -d 编译结果文件

  1. npx babel js/a.js -o js/b.js

配置

  1. {
  2. "presets": ["@babel/preset-env"]
  3. }

兼容的浏览器
@babel/preset-env 需要根据兼容的浏览器范围来确定如何编译,如postcss一样,可以使用文件 .browserslistrc 来描述浏览器的兼容范围

  1. last 3 version
  2. > 1%
  3. not ie <= 8

自身的配置

  1. {
  2. "presets": [
  3. ["@babel/preset-env",{
  4. "配置项1":"配置值"
  5. }]
  6. ]
  7. }

比较常见的配置项是 usebuiltins ,默认值是false
由于该预设仅转换新的语法,并不对新的api进行任何处理

babel 插件

  • @babel/plugin-proposal-optional-chaining

安全性的读取

  1. const obj = {
  2. foo:{
  3. bar:{
  4. baz:42
  5. }
  6. }
  7. }
  8. const baz = obj?.foo?.bar?.baz //42
  • babel-plugin-transform-remove-console

该插件会移除源码中的控制台输出语句

  • @babel/plugin-transform-runtime

用于提供一些公共的API 这些api会帮助代码转换

在webpack中使用babel

  1. npm i -D @babel/core babel-loader
  1. module.exports = {
  2. mode: "development",
  3. devtool: 'source-map',
  4. module: {
  5. rules: [{
  6. test: /\.js$/,
  7. use: "babel-loader"
  8. }]
  9. }
  10. }

拓展课程 未学习

五:性能优化

性能优化概述

不要过早的去关注性能

构建性能
指的是开发阶段的构建性能。优化的目标,是降低从打包开始,到代码效果呈现所经过的时间

传输性能
指的是js代码传输到浏览器经过的时间
1.总传输量
2.文件数量 即js文件数量
3.浏览器缓存

运行性能
取决于如何书写高性能的代码

减少模块解析

模块解析 :抽象语法树分析 依赖分析 模块语法替换

哪些模块不需要解析
模块中无其它依赖,一些已经打包好的第三方库,比如 jquery
配置 module.noParse 正则表达式

  1. module.exports ={
  2. mode:"development",
  3. module:{
  4. noParse:/jquery/
  5. }
  6. }

对比打包时间
image.png
image.png

优化loader性能

  • 限制loader 的应用范围

**
对于某些库,不使用 loader

例如,babel-loader 可以转换ES6或更高版本的语法,可是有些库本身就是用ES5语法写的,不需要转换,使用babel-loader 反而浪费构建时间 loadsh

module.rule.exclude module.rule.include

  1. module.exports = {
  2. module:{
  3. rules:[
  4. {
  5. test:/.\js$/,
  6. exclude:/lodash/,
  7. use:"babel-loader"
  8. }
  9. ]
  10. }
  11. }
  • 缓存loader的结果

如果某个文件内容不变,经过相同的loader解析后,解析后的结果也不变。

cache-loader 放到最前面却能够决定后续的loader是否运行。
实际上,loader运行的过程中,还包含一个过程,即 pitch

  1. module.exports = {
  2. mode:'development',
  3. devtool:'source-map',
  4. module:{
  5. rules: [{
  6. test: /.\js$/,
  7. use: [{
  8. loader: "cache-loader",
  9. options: {
  10. cacheDirectory: './cache'
  11. }
  12. }, "babel-loader"]
  13. }]
  14. }
  15. }
  • 为 loader 的运行开启多线程

thread-loader 会开启一个线程池,线程池中包含适量的线程

它会把后续的loader 放到线程池的线程中运行,以提高构建效率
由于后续的loader会放到新的线程中,所以,后续的loader不能

  • 使用webpack api 生成文件
  • 无法使用定义的 plugin api
  • 无法访问 webpack options

热替换 HMR

webpack-dev-server 的区别
dev-server: 代码变动 => 浏览器刷新 重新请求所有资源
热替换 : 代码变动 => 浏览器仅请求改动的资源

  1. const HtmlWebpackPlugin = require('html-webpack-plugin')
  2. module.exports = {
  3. mode:"development",
  4. devtool: "source-map",
  5. devServer:{
  6. open:true,
  7. hot:true
  8. },
  9. plugins:[new HtmlWebpackPlugin({
  10. template:"./public/index.html"
  11. })]
  12. }
  1. import a from "./a";
  2. console.log(a)
  3. if(module.hot){
  4. //是否开启了热更新
  5. module.hot.accept() //接受热更新
  6. }

当开启了热更新后,webpack-dev-server 会向打包结果中注入 module.hot 属性
默认情况下,webpack-dev-server 不管是否开启了热更新,当重新打包后,都会调用 location.reload刷新页面,但是如果运行了 module.hot.accept() ,将改变这一行为
module.hot.accept() 的作用是让 webpack-dev-server 通过 socket 管道,把服务器更新的内容发送到浏览器,然后将结果交给插件 HotModuleReplacementPlugin 注入的代码执行。
热替换发生在代码运行期

替换过程
image.png

手动分包 (传输性能)

dll : dynamic link library 动态链接库

原理:
1.先单独的打包公共模块

单独配置 webpack.dll.config.js 文件进行打包

  1. const webpack = require("webpack");
  2. const path = require("path")
  3. module.exports = {
  4. mode:'production',
  5. entry:{
  6. jquery:['jquery'],
  7. lodash:['lodash']
  8. },
  9. output:{
  10. filename:'dll/[name].js',
  11. library:"[name]" //每个bundle暴露的全局变量名
  12. },
  13. plugins:[
  14. new webpack.DllPlugin({
  15. path:path.resolve(__dirname,"dll","[name].manifest.json"),
  16. name:"[name]"
  17. })
  18. ]
  19. }

webpack.DllPlugin webpack 自带的工具
image.png

自动分包

代码压缩

使用什么压缩工具?
目前最流行的代码压缩工具主要有两个, UglifyJSTerser

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

两次打包对比
image.png

image.png

作用域分析 (不推荐)
tree-shaking 本身并没有完善的作用域分析,可能导致一些 dead code 函数中的依赖仍然被视为依赖
webpack-deep-scope-plugin 提供了作用域分析,可解决这些问题

css tree shaking
purgecss-webpack-plugin

懒加载

**

Eslint(其它优化)

代码风格检查

安装使用
npm i -D eslint
image.png
image.png

配置项
env
配置代码的运行环境

  • browser : 代码是否在浏览器环境中运行
  • es6 : 是否启用es6的全局api 例如 Promise

parseroptions
该配置指定 eslint 对哪些语法的支持

  • ecmaVersion: 支持的ES语法版本
  • sourceType
    • script 传统脚本
    • module 模块化脚本

rules
查阅文档

bundle analyzer

bundle分析
webpack-bundle-analyzer
image.png

gzip

使用 compression-webpack-plugin 插件对打包结果进行预压缩,可以移除服务器的压缩时间