Webpack 基础学习笔记

Webpack 基础学习笔记

Webpack基础使用

  • webpack的安装
npm init -y

npm install webpack webpack-cli -D
  • webpack配置文件的基础使用
const path = require('path');
module.exports = {
    // 入口
    entry: './src/index.js',
    output: {
        // 输出目录
        path: path.resolve(__dirname, 'dist'),
        // 编译后的文件名
        filename: 'bundle.js',
    },
};

常用loader

  • css-loader、style-loader的使用

css-loader用于解析CSS包括设置的背景图
style-loader用于将CSS插入到页面中生效

npm install --save-dev css-loader style-loader less-loader

const path = require('path');
module.exports = {
  entry: "./src/index.js",
  output: {
    filename: "bundle.js",
    // 必须是一个绝对路径
    path: path.resolve(__dirname, "./build")
  },
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: ["style-loader", "css-loader", "less-loader"],
      },
    ],
  },
}
  • less-loader

less-loader用于编写less

npm install less less-loader --save-dev

module: {
    rules: [
      {
        test: /\.css$/i,
        use: ["style-loader", "css-loader"],
      },
      {
          test: /\.less$/i,
          use: ["style-loader", "css-loader", "less-loader"]
      }
    ],
}
  • postcss-loader

postcss-loader可以帮助我们进行一些CSS的转换和适配,比如自动添加浏览器前缀、css样式的重置。但是实现这些工具,我们需要借助于PostCSS对应的插件。

npm install postcss-loader postcss-preset-env -D

// 第一步配置loader
module: {
    rules: [
      {
        test: /\.css$/i,
        use: ["style-loader", "css-loader", "postcss-loader"],
      },
      {
          test: /\.less$/i,
          use: ["style-loader", "css-loader", "less-loader", "postcss-loader"]
      }
    ],
}

// 第二步配置插件
在根目录创建postcss.config.js

module.exports = {
    plugins: [
      'postcss-preset-env'
    ]
  }
  • url-loader、file-loader

url-loader、file-loader用来加载图片等资源

  • asset module资源模块

它允许使用资源文件(字体,图标等)而无需配置额外 loader。

加载字体和图片的配置
module:{
    rules: [
            {
                    test: /\.(png|jpe?g|gif|svg)$/,
                    type: "asset",
                    generator: {
                      filename: "img/[name][ext]"
                    },
                    parser: {
                      dataUrlCondition: {
                        maxSize: 100 * 1024
                      }
                    }
              },
              {
                    test: /\.ttf|eot|woff2?$/i,
                    type: "asset/resource",
                    generator: {
                      filename: "font/[name].[ext]"
                    }
              }
    ]
}
  • 浏览器市场占有率查询

浏览器市场占有率查询

browserslist工具
Browserslist是一个在不同的前端工具之间,共享目标浏览器和Node.js版本的配置

Browserslist编写规则:

defaults:Browserslist的默认浏览器(> 0.5%, last 2 versions, Firefox ESR, not dead)。
5%:通过全局使用情况统计信息选择的浏览器版本。 >=,<和<=工作过。
	1、5% in US:使用美国使用情况统计信息。它接受两个字母的国家/地区代码。
	2、> 5% in alt-AS:使用亚洲地区使用情况统计信息。有关所有区域代码的列表,请参见caniuse-lite/data/regions
	3、> 5% in my stats:使用自定义用法数据。
	4、> 5% in browserslist-config-mycompany stats:使用 来自的自定义使用情况数据browserslist-config-mycompany/browserslist-stats.json。  
	5、cover 99.5%:提供覆盖率的最受欢迎的浏览器。
	6、cover 99.5% in US:与上述相同,但国家/地区代码由两个字母组成。
	7、cover 99.5% in my stats:使用自定义用法数据。

dead:24个月内没有官方支持或更新的浏览器。现在是IE 10,IE_Mob 11,BlackBerry 10,BlackBerry 7, Samsung 4和O eraMobile 12.1。

last 2 versions:每个浏览器的最后2个版本。
	1、last 2 Chrome versions:最近2个版本的Chrome浏览器。
	2、last 2 major versions或last 2 iOS major versions:最近2个主要版本的所有次要/补丁版本。

Browserslist配置文件的编写
方案一:在package.json中配置

"browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
},

方案二:在项目根目录创建 .browserslistrc文件

> 0.2%
last 2 version
not dead 

常用Plugin插件

  • CleanWebpackPlugin

CleanWebpackPlugin每次构建打包自动删除输出目录

const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
  entry: "./src/main.js",
  output: {
    filename: "js/bundle.js",
    path: path.resolve(__dirname, "./build"),
  },
  plugins: [
    new CleanWebpackPlugin(),
  ]
}
  • HtmlWebpackPlugin

HtmlWebpackPlugin可以为项目生成一个html文件,使用默认模板或者自定义模板。

npm install --save-dev html-webpack-plugin

plugins: [
    new HtmlWebpackPlugin({
      title: "模板title",
      template: "./public/index.html" // 自定义模板路径
    }),
]

index.html文件内容

<!DOCTYPE html>
<html lang="">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>
  <body>
    <noscript>
      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="app"></div>
  </body>
</html>

title采用的是ejs模板填充数据
  • DefinePlugin

DefinePlugin允许在 编译时 将你代码中的变量替换为其他值或表达式。

在上面自定义模板中有一个BASE_URL就需要DefinePlugin利用来配置

const { DefinePlugin } = require('webpack');
new DefinePlugin({
  BASE_URL: '"./"'
}),
  • CopyWebpackPlugin

CopyWebpackPlugin用于将一些静态资源复制到webpack输出的打包路径下。

npm install copy-webpack-plugin --save-dev

const CopyWebpackPlugin = require('copy-webpack-plugin');

new CopyWebpackPlugin({
  patterns: [
    {
      from: "public",
      globOptions: {
        ignore: [
          "**/index.html",
          "**/.DS_Store",
          "**/abc.txt"
        ]
      }
    }
  ]
})

source-map

  • Mode的配置
module.exports = {
  mode: 'development',  // 'production': 'none' | 'development' | 'production'
};

production为默认值
选项描述
development会将 DefinePlugin 中 process.env.NODE_ENV 的值设置为 development. 为模块和 chunk 启用有效的名
production会将 DefinePlugin 中 process.env.NODE_ENV 的值设置为 production。为模块和 chunk 启用确定性的混淆名称,FlagDependencyUsagePlugin,FlagIncludedChunksPlugin,ModuleConcatenationPlugin,NoEmitOnErrorsPlugin 和 TerserPlugin
none不使用任何默认优化选项
  • source-map

source-map是从已转换的代码,映射到原始的源文件。使浏览器可以重构原始源并在调试器中显示重建的原始源。

如何使用source-map?
第一步:根据源文件,生成source-map文件,webpack在打包时,可以通过配置生成source-map
第二步:在转换后的代码,最后添加一个注释,它指向sourcemap

//# sourceMappingURL=bundle.js.map

浏览器会根据我们的注释,查找响应的source-map,并且根据source-map还原我们的代码,方便进行调试。
第三步:在Chrome中启用source-map

source-map的结构:

{
    // 版本
    "version": 3, 
    // 打包后的文件 
    "file": "bundle.js",
    // source-map用来和源文件映射的信息(比如位置信息等),一串base64 VLQ(veriable- length quantity可变长度值)编码
    "mappings": "",
    // 从哪些文件转换过来的source-map和打包的代码(最初始的文件)
    "sources": [
        "webpack://demo/./src/css/index.less",
        "webpack://demo/./src/css/index.css"
    ],
    // 转换前的具体代码信息和sources是对应的关系
    "sourcesContent": [
      
    ],
    // 转换前的变量和属性名称
    "names": [],
    // 所有的sources相对的根目录
    "sourceRoot": ""
}

source-map的生成:

通过配置devtool来设置

module.exports = {
  mode: "development",
  entry: "./src/index.js",
  devtool: "source-map" // eval false none
}

false:不使用source-map,也就是没有任何和source-map相关的内容
none:production模式下的默认值,不生成source-map。
eval:development模式下的默认值,不生成source-map
但是它会在eval执行的代码中,添加

//# sourceURL=webpack://demo/./node_modules/style-loader/dist/runtime/setAttributesWithoutAttributes.js

它会被浏览器在执行时解析,并且在调试面板中生成对应的一些文件目录,方便我们调试代码;

常用devtool值的说明
source-map:
生成一个独立的source-map文件,并且在bundle文件中有一个注释,指向source-map文件;
bundle文件中有如下的注释:
//# sourceMappingURL=bundle.js.map
开发工具会根据这个注释找到source-map文件,并且解析;

eval-source-map:
会生成sourcemap,但是source-map是以DataUrl添加到eval函数的后面

inline-source-map:
会生成sourcemap,但是source-map是以DataUrl添加到bundle文件的后面

cheap-source-map:
会生成sourcemap,但是会更加高效一些(cheap低开销),因为它没有生成列映射(Column Mapping)

cheap-module-source-map:
会生成sourcemap,类似于cheap-source-map,但是对源自loader的sourcemap处理会更好

hidden-source-map:
会生成sourcemap,但是不会对source-map文件进行引用; 相当于删除了打包文件中对sourcemap的引用注释;

 // 被删除掉的
//# sourceMappingURL=bundle.js.map

nosources-source-map:
会生成sourcemap,但是生成的sourcemap只有错误信息的提示,不会生成源代码文件

推荐设置:
开发阶段:推荐使用 source-map或者cheap-module-source-map。这分别是vue和react使用的值,可以获取调试信息,方便快速开发;

测试阶段:推荐使用 source-map或者cheap-module-source-map。测试阶段我们也希望在浏览器下看到正确的错误提示;

发布阶段:false、缺省值(不写)

Babel

Babel是一个工具链,主要用于旧浏览器或者缓解中将ECMAScript 2015+代码转换为向后兼容版本的JavaScript。包括:语法转换、源代码转换、Polyfill实现目标缓解缺少的功能等。

  • Babel对代码编译

1、安装依赖

npm install --save-dev @babel/core @babel/cli @babel/preset-env babel-loader

@babel/cli 命令行依赖

2、配置webapck.config.json

 module: {
    rules: [
        {
        test: /\.m?js$/,
        use: {
                loader: "babel-loader",
            },
        }
    ]
}

3、配置babel.config.json

{
    "presets": [
        ["@babel/preset-env", {
            "targets": "chrome 88" // 浏览器版本配置 last 2 version
        }]
    ],
}
  • Polyfill

使用了一些语法特性(例如:Promise, Generator, Symbol等以及实例方法例如Array.prototype.includes等),但是某些浏览器压根不认识这些特性,必然会报错; 可以使用polyfill来填充或者说打一个补丁,那么就会包含该特性了;

  • Babel对React的编译

1、安装react预设

npm install --save-dev @babel/preset-react babel-loader

2、在根目录下面增加babel配置

babel.config.json

{
    "presets": ["@babel/preset-react"]
}

3、在webpack.config.json中增加对jsx的编译支持

module: {
    rules: [
        {
              test: /\.jsx$/,
              use: {
                  loader: 'babel-loader'
              }
          }
    ]
}
  • Babel对Vue的编译

1、安装相关依赖

 npm install vue-loader vue-template-compiler -D
 

2、配置webpack

module: {
    rules: [
        {
            test: /\.vue$/,
            use: {
                loader: "vue-loader",
            }
        }
    ]
}

const VueLoaderPlugin = require('vue-loader/lib/plugin');
plugins: [
    new VueLoaderPlugin()
],
  • Babel对TypeScript的编译

1、安装typescript预设

npm install --save-dev @babel/preset-typescript

2、配置babel.config.json

{
    "presets": [
        "@babel/preset-typescript"
    ]
}

3、配置webpack

module: {
    rules: [
        {
            test: /\.ts$/,
            use: {
                loader: 'babel-loader'
            }
        }
    ]
}

ESlint

ESLint是一个静态代码分析工具。

  • 安装以及初始化
npm install eslint -D
 
创建eslint的配置
npx eslint --init

module.exports = {
    "env": {
        "browser": true,
        "es2021": true
    },
    "extends": "eslint:recommended",
    "parserOptions": {
        "ecmaVersion": 13
    },
    "rules": {
    }
};

eslint默认创建的环境如下:
1、env:运行的环境,比如是浏览器,并且我们会使用es2021(对应的ecmaVersion是12)的语法;
2、extends:可以扩展当前的配置,让其继承自其他的配置信息,可以跟字符串或者数组(多个);
3、arserOptions:这里可以指定ESMAScript的版本、sourceType的类型
parser:默认情况下是espree(也是一个JS Parser,用于ESLint),但是因为我们需要编译TypeScript,所以需要指定对应的解释器;
4、plugins:指定我们用到的插件;
5、rules:自定义的一些规则;

VSCode的Prettier - Code formatter插件可以修复代码格式问题。
Setting -> formatter -> default Formatter配置成刚安装的插件。

  • webpack中使用eslint-loader
npm install eslint-loader -D

module: {
    rules: [
        {
            test: /\.js$/,
            use: [
                'babel-loader',
                'eslint-loader',
            ]
        }
    ]
}

本地服务器

本地服务器的方案:
1、webpack watch mode;
2、pwebpack-dev-server;
3、pwebpack-dev-middleware

  • webpack watch mode

webpack开启watch mode的方式:

1、在配置文件中添加watch配置

module.exports = {
  watch: true,
}

2、在package.json添加script脚本

"scripts": {
    "watch": "webpack --watch"
}

这种方式会监听文件变化后重新编译,但是不会自动刷新浏览器。

  • webpack-dev-server

1、安装依赖

安装
npm install --save-dev webpack-dev-server

2、在package.json中添加脚本

"scripts": {
    "serve": "webpack serve"
},

在命令行运行npm run serve会开启一个服务加载显示开发的页面。具备实时重载刷新页面的功能。
webpack-dev-server 在编译之后不会写入到任何输出文件。而是将 bundle 文件保留在内存中。

热替换(HMR):在应用程序运行过程中,替换、添加、删除模块,而无需重新刷新整个页面;
webpack-dev-server开启HMR

module.exports = {
  devServer: {
      hot: true
  },
}

框架的HMR:
1、React

1、安装依赖
npm install -D @pmmmwh/react-refresh-webpack-plugin react-refresh
 
2、配置babel.config.json
{
    "presets": [
        "@babel/preset-react",
    ],
    "plugins": [
        "react-refresh/babel"
    ]
}
3、配置webpack.config
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');

plugins: [
    new ReactRefreshWebpackPlugin()
  ],

2、Vue

vue-loader加载的组件默认会帮助我们进行HMR的处理

HMR原理:

webpack-dev-server会创建两个服务:提供静态资源的服务(express)和Socket服务(net.Socket);
express server负责直接提供静态资源的服务(打包后的资源直接被浏览器请求和解析);

HMR Socket Server,是一个socket的长连接: 1、长连接有一个最好的好处是建立连接后双方可以通信(服务器可以直接发送文件到客户端); 2、当服务器监听到对应的模块发生变化时,会生成两个文件.json(manifest文件)和.js文件(update chunk);
3、通过长连接,可以直接将这两个文件主动发送给客户端(浏览器);
4、浏览器拿到两个新的文件后,通过HMRruntime机制,加载这两个文件,并且针对修改的模块进行更新;

  • webpack中output的publicPath说明:

output中的path的作用是告知webpack之后的输出目录: 比如静态资源的js、css等输出到哪里,常见的会设置为dist、build文件夹等;

output中还有一个publicPath属性,该属性是指定index.html文件打包引用的一个基本路径:
1、它的默认值是一个空字符串,所以我们打包后引入js文件时,路径是 bundle.js;
2、在开发中,我们也将其设置为 / ,路径是 /bundle.js,那么浏览器会根据所在的域名+路径去请求对应的资源;
3、如果我们希望在本地直接打开html文件来运行,会将其设置为 ./,路径时 ./bundle.js,可以根据相对路径去 查找资源;

  • devServer常用配置

publicPath:
devServer中也有一个publicPath的属性,该属性是指定本地服务所在的文件夹:

1、它的默认值是/,也就是我们直接访问端口即可访问其中的资源http://localhost:8080;
2、如果我们将其设置为了/abc,那么我们需要通过http://localhost:8080/abc 才能访问到对应的打包后的资源;
3、并且这个时候,我们其中的bundle.js通过http://localhost:8080/bundle.js 也是无法访问的:
所以必须将output.publicPath也设置为/abc;
官方其实有提到,建议devServer.publicPath与output.publicPath相同;

contentBase:

devServer中contentBase对于我们直接访问打包后的资源其实并没有太大的作用,它的主要作用是如果我们打包后的资源,又依赖于其他的一些资源,那么就需要指定从哪里来查找这个内容:

1、比如在index.html中,我们需要依赖一个abc.js文件,这个文件我们存放在public文件中;
2、在index.html中,我们应该如何去引入这个文件呢?

比如代码是这样的:

<script src="./public/abc.js"></script>

但是这样打包后浏览器是无法通过相对路径去找到这个文件夹的,所以代码是这样的

<script src="/abc.js"></script>

但是我们如何让它去查找到这个文件的存在呢? 设置contentBase即可;

当然在devServer中还有一个可以监听contentBase发生变化后重新编译的一个属性:watchContentBase。

host:
设置主机地址默认值是localhost.如果希望其他地方也可以访问,可以设置为0.0.0.0。

port:设置监听的端口,默认情况下是8080

open:是否打开浏览器,默认值是false,设置为true会打开浏览器

ncompress: 是否为静态文件开启gzip compression

proxy:我们开发中非常常用的一个配置选项,它的目的设置代理来解决跨域访问的问题.

比如我们的一个api请求是 http://localhost:8888, 但是本地启动服务器的域名是 http://localhost:8000, 这个时候发送网络请求就会出现跨域的问题;
那么我们可以将请求先发送到一个代理服务器,代理服务器和API服务器没有跨域的问题,就可以解决我们的跨域问题了;

我们可以进行如下的设置:
1、target:表示的是代理到的目标地址,比如/api/moment会被代理到http://localhost:8888/api/moment;
2、pathRewrite:默认情况下,我们的/api也会被写入到URL中,如果希望删除,可以使用pathRewrite;
3、secure:默认情况下不接收转发到https的服务器上,如果希望支持,可以设置为false; 4、changeOrigin:它表示是否更新代理后请求的headers中host地址;

historyApiFallback:
historyApiFallback是开发中一个非常常见的属性,它主要的作用是解决SPA页面在路由跳转之后,进行页面刷新时,返回404的错误。

默认是false如果设置为true,那么在刷新时,返回404错误时,会自动返回index.html的内容;
object类型的值,可以配置rewrites属性,可以配置from来匹配路径,决定要跳转到哪一个页面;

historyApiFallback: {
  rewrites: [
    {from: /abc/, to: "/index.html"}
  ]
}

resolve模块

resolve用于设置模块如何被解析:
在开发中我们会有各种各样的模块依赖,这些模块可能来自于自己编写的代码,也可能来自第三方库;
resolve可以帮助webpack从每个require/import 语句中,找到需要引入到合适的模块代码; webpack 使用enhanced-resolve来解析文件路径;

1、resolve.extensions选项作为文件扩展名解析
2、resolve.mainFiles配置选项中指定的文件顺序查找
3、配置别名alias

环境分离和代码分离

  • 开发环境和生产环境配置文件的分离

1、编写两个不同的配置文件,开发和生产时,分别加载不同的配置文件即可
2、使用相同的一个入口配置文件,通过设置参数来区分它们

1、创建三个不同的配置文件webpack.common.js、webpack.dev.js、webpack.prod.js编写不同的配置
2、在webpack.common.js中导入不同环境的配置,并合并
const prodConfig = require("./webpack.prod");
const devConfig = require("./webpack.dev");
const { merge } = require("webpack-merge");
const commonConfig = {
    // 公共配置
} 
module.exports = function(env) {
    // 根据环境变量来合并配置
    const isProduction = env.production;
    process.env.NODE_ENV = isProduction ? "production" : "development";
    
    const config = isProduction ? prodConfig : devConfig;
    const mergeConfig = merge(commonConfig, config);
    return mergeConfig;
};

3、在package.config.json添加脚本配置
"scripts": {
    "build": "webpack --config ./config/webpack.common.js --env production",
    "serve": "webpack serve --config ./config/webpack.common.js --env development"
},
  • 代码分离

入口起点:使用entry配置手动分离代码;
防止重复:使用Entry Dependencies或者SplitChunksPlugin去重和分离代码;
动态导入:通过模块的内联函数调用来分离代码;

1、多入口配置:

module.exports = {
  mode: "development",
  entry: {
    index: "./src/index.js",
    main: './src/main.js'
  },
  output: {
    filename: "[name].bundle.js",
    path: path.resolve(process.cwd(), "./build"),
  },
}

2、使用Entry Dependencies或者SplitChunksPlugin去重和分离代码

Entry Dependencies(入口依赖):
假如我们的index.js和main.js都依赖dayjs库
如果我们单纯的进行入口分离,那么打包后的两个bunlde都有会有一份dayjs;
事实上我们可以对他们进行共享

// 不推荐使用
module.exports = {
  mode: "production", // production development
  entry: {
    // index: "./src/index.js",
    // main: './src/main.js'
    index: {import: "./src/index.js", dependOn: "shared"},
    main: {import: "./src/main.js", dependOn: "shared"},
    shared: ['dayjs']
  },
}

SplitChunks
另外一种分包的模式是splitChunk,它是使用SplitChunksPlugin来实现的:
因为该插件webpack已经默认安装和集成,所以我们并不需要单独安装和直接使用该插件,只需要提供SplitChunksPlugin相关的配置信息即可.

Webpack提供了SplitChunksPlugin默认的配置,我们也可以手动来修改它的配置.
比如默认配置中,chunks仅仅针对于异步(async)请求,我们可以设置为initial或者all

split-chunks-plugin文档

splitChunks: {
      // async异步导入
      // initial同步导入
      // all 异步/同步导入  
      chunks: "all",
      // 最小尺寸: 如果拆分出来一个, 那么拆分出来的这个包的大小最小为minSize
      minSize: 20000,
      // 将大于maxSize的包, 拆分成不小于minSize的包
      maxSize: 20000,
      // minChunks表示引入的包, 至少被导入了几次,如果我们写一个2,但是引入了一次,那么不会被单独拆分
      minChunks: 1,
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          filename: "[id]_vendors.js",
          // name: "vendor-chunks.js",
          priority: -10
        },
        default: {
          minChunks: 2,
          filename: "common_[id].js",
          priority: -20
        }
      }
    }

动态导入查看官方文档。

  • optimization优化配置

1、chunkIds告知webpack当选择模块id时需要使用哪种算法。
natural:按照数字的顺序使用id;
named:development下的默认值,一个可读的名称的id。
pdeterministic:在不同的编译中不变的短数字id。有益于长期缓存。在生产模式中会默认开启。

optimization: {
    chunkIds: "named",
}

2、runtimeChunk:配置runtime相关的代码是否抽取到一个单独的chunk中
runtime相关的代码指的是在运行环境中,对模块进行解析、加载、模块信息相关的代码;
比如我们的component通过import函数相关的代码加载,就是通过runtime代码完成的;

抽离出来后,有利于浏览器缓存的策略:
比如我们修改了业务代码(main),那么runtime和component的chunk是不需要重新加载的;
比如我们修改了component的代码,那么main中的代码是不需要重新加载的;

设置的值:
true/multiple:针对每个入口打包一个runtime文件;
single:打包一个runtime文件;
对象:name属性决定runtimeChunk的名称

optimization: {
    runtimeChunk: {
      name: 'runtime',
    },
}
  • Prefetch和Preload

在声明import时,使用下面这些内置指令,可以让webpack输出"resource hint(资源提示)",来告知浏览器:

prefetch(预获取):将来某些导航下可能需要的资源
preload(预加载):当前导航下可能需要资源

与prefetch指令相比,preload指令有许多不同之处:
preload chunk会在父chunk加载时,以并行方式开始加载。prefetch chunk会在父 chunk加载结束后开始加载。
preload chunk具有中等优先级,并立即下载。prefetch chunk在浏览器闲置时下载。
preload chunk会在父chunk中立即请求,用于当下时刻。prefetch chunk会用于未来的某个时刻

  • CDN使用方式

方式一:打包的所有静态资源,放到CDN服务器,用户所有资源都是通过CDN服务器加载的

可以直接修改publicPath,在打包时添加上自己的CDN地址
output: {
    publicPath: "http://cdn.example.com/[fullhash]/",
}

方式二:一些第三方资源放到CDN服务器上

通常一些比较出名的开源框架都会将打包后的源码放到一些比较出名的、免费的CDN服务器上:
1、国际上使用比较多的是unpkg、JSDelivr、cdnjs
2、国内也有一个比较好用的CDN是bootcdn

项目中使用CDN:
1、我们可以通过webpack配置,来排除一些库的打包
2、在html模块中,加入CDN服务器地址

module.exports = {
  // 排除
  externals: {
    jquery: 'jQuery',
  },
};

// 在html中引入对应库的CDN地址
<script
  src="https://code.jquery.com/jquery-3.1.0.js"
  integrity="sha256-slogkvB1K3VOkzAI8QITxV3VzpOnkeNVsKvtkYLMjfk="
  crossorigin="anonymous"
></script>
  • Hash、ContentHash、ChunkHash

在我们给打包的文件进行命名的时候,会使用placeholder,placeholder中有几个属性比较相似: hash、chunkhash、contenthash
hash本身是通过MD4的散列函数处理后,生成一个128位的hash值(32个十六进制);

hash值的生成和整个项目有关系:
1、比如我们现在有两个入口index.js和main.js;
2、它们分别会输出到不同的bundle文件中,并且在文件名称中我们有使用hash;
3、这个时候,如果修改了index.js文件中的内容,那么hash会发生变化;
4、那就意味着两个文件的名称都会发生变化;

chunkhash可以有效的解决上面的问题,它会根据不同的入口进行借来解析来生成hash值:
比如我们修改了index.js,那么main.js的chunkhash是不会发生改变的;

contenthash表示生成的文件hash名称,只和内容有关系:
1、比如我们的index.js,引入了一个style.css,style.css有被抽取到一个独立的css文件中;
2、这个css文件在命名时,如果我们使用的是chunkhash;
3、那么当index.js文件的内容发生变化时,css文件的命名也会发生变化;
4、这个时候我们可以使用contenthash;

  • MiniCssExtractPlugin

MiniCssExtractPlugin将CSS提取到单独的文件中,为每个包含CSS的JS文件创建一个CSS文件,并且支持CSS和SourceMaps的按需加载。

npm install mini-css-extract-plugin -D

const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {
  plugins: [
          new MiniCssExtractPlugin({
              filename: "css/[name].[contenthash:6].css"
          })],
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: [MiniCssExtractPlugin.loader, "css-loader"],
      },
    ],
  },
};

js和css代码压缩

  • JavaScript代码压缩(TerserWebpackPlugin)
npm install terser-webpack-plugin --save-dev

const TerserPlugin = require("terser-webpack-plugin");

module.exports = {
  optimization: {
    minimize: true,
    minimizer: [new TerserPlugin()],
  },
};
  • css的压缩(CssMinimizerWebpackPlugin)
npm install css-minimizer-webpack-plugin --save-dev

const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

module.exports = {
  module: {
    rules: [
      {
        test: /.s?css$/,
        use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"],
      },
    ],
  },
  optimization: {
    minimizer: [
      new CssMinimizerPlugin(),
    ],
  },
  plugins: [new MiniCssExtractPlugin()],
};

Tree Shaking

Tree Shaking是一个术语,通常用于描述移除 JavaScript 上下文中的未引用代码(dead-code)。

  • JavaScript的Tree Shaking

webpack实现Tree Shaking采用了两种不同的方案:
1、usedExports:通过标记某些函数是否被使用,之后通过Terser来进行优化的
2、sideEffects:跳过整个模块/文件,直接查看该文件是否有副作用

usedExports

optimization: {
   usedExports: true,
 }

sideEffects
sideEffects用于告知webpack compiler哪些模块时有副作用的,副作用的意思是这里面的代码有执行一些特殊的任务,不能仅仅通过export来判断这段代码的意义.

在package.json中设置sideEffects的值:
如果我们将sideEffects设置为false,就是告知webpack可以安全的删除未用到的exports;
如果有一些我们希望保留,可以设置为数组.
我们有一个formatData.js、style.css文件需要保留

package.json

"sideEffects": ["*.css", "./src/js/formatData.js"],

还有一种css的处理方式
在webpack.config.js中
module: {
    rules: [
          {
            test: /\.css$/i,
            use: [MiniCssExtractPlugin.loader, "css-loader", "postcss-loader"],
            sideEffects: true
          }
      ]
}
  • css的Tree Shaking

CSS的Tree Shaking需要借助PurgeCSS插件

 npm install purgecss-webpack-plugin glob -D
 
const PurgeCssPlugin = require('purgecss-webpack-plugin');
const glob = require('glob');

 new PurgeCssPlugin({
      paths: glob.sync(`${path.resolve(__dirname, '../src')}/**/*`,  { nodir: true }),
}),

代码压缩

HTTP压缩是一种内置在 服务器 和 客户端 之间的,以改进传输速度和带宽利用率的方式。
第一步:HTTP数据在服务器发送前就已经被压缩了;(可以在webpack中完成)
第二步:兼容的浏览器在向服务器发送请求时,会告知服务器自己支持哪些压缩格式
第三步:服务器在浏览器支持的压缩格式下,直接返回对应的压缩后的文件,并且在响应头中告知浏览器

  • Webpack对文件压缩

webpack中相当于是实现了HTTP压缩的第一步操作,我们可以使用CompressionPlugin

npm install compression-webpack-plugin -D

插件配置压缩css和js
const CompressionPlugin = require('compression-webpack-plugin');

new CompressionPlugin({
  test: /\.(css|js)$/i,
  threshold: 0,
  minRatio: 0.8,
  algorithm: "gzip",
  // exclude
  // include
}),
  • HTML文件中代码的压缩

HtmlWebpackPlugin插件来生成HTML的模板,事实上它还有一些其他的配置
inject:设置打包的资源插入的位置(true、 false 、body、head)
cache:设置为true,只有当文件改变时,才会生成新的文件(默认值也是true)
minify:默认会使用一个插件html-minifier-terser

new HtmlWebpackPlugin({
  template: "./index.html",
  cache: true, // 当文件没有发生任何改变时, 直接使用之前的缓存
    minify: {
      removeComments: false, // 是否要移除注释
      removeRedundantAttributes: true, // 是否移除多余的属性
      removeEmptyAttributes: true, // 是否移除一些空属性
      collapseWhitespace: true,
      removeStyleLinkTypeAttributes: true,
      minifyCSS: true,
      minifyJS: {
        mangle: {
          toplevel: true
        }
      }
    }
})
  • InlineChunkHtmlPlugin

有一个插件,可以辅助将一些chunk出来的模块,内联到html中:
1、比如runtime的代码,代码量不大,但是是必须加载的
2、那么我们可以直接内联到html中

npm install react-dev-utils -D
 
module.exports = {
     optimization: {
        runtimeChunk: {
            name: 'runtime'
        },
     },
     plugins: [
         new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/runtime.*\.js/,])
     ]
}

自定义loader

  • custom-loader的创建和配置(normal loader)
// custom-loader.js
module.exports = function(content, map, meta) {
    console.log('我的loader');
    console.log(content);
    return content;
}

// webpack.config.js配置
module.exports = {
  module: 
    rules: [{
      test: /\.js$/,
      // loader路径
      path.resolve(__dirname, '../src/loader/custom-loader.js')
    }]
  }
};

resolveLoader属性:配置loader的相关信息,包括loader路径等

// webpack.config.js配置
module.exports = {
  module: 
    rules: [{
      test: /\.js$/,
      'custom-loader'
    }]
  },
  resolveLoader: {
    modules: ['node_modules', path.resolve(__dirname, '../src/loader/')],
  },
};

自定义loader的加载顺序和第三方是一样的,根据配置顺序从后往前,从右向左。

  • pitch-loader
// normal loader
module.exports = function(content, map, meta) {
    console.log('我的loader');
    console.log(content);
    return content;
}

// pitch loader
module.exports.pitch = function() {
    console.log('loader pitch');
}

pitch loader的执行顺序:

module.exports = {
  module: 
    rules: [{
      test: /\.js$/,
      'custom-loader',
      'custom-loader1',
    }]
  },
  resolveLoader: {
    modules: ['node_modules', path.resolve(__dirname, '../src/loader/')],
  },
};

pitch loader按照上面的配置顺序从上往下执行,noamal-loader的执行顺序则相反从后往前。

能不能改变它们的执行顺序呢?
我们可以拆分成多个Rule对象,通过enforce来改变它们的顺序

{
    test: /\.js$/,
    use: [
      'custom-loader'
    ],
    },
    {
    test: /\.js$/,
    use: [
      'custom-loader1'
    ],
},

// 输出结果
loader pitch    
loader pitch1
我的loader1
我的loader

{
    test: /\.js$/,
    use: [
      'custom-loader'
    ],
    enforce: "pre"  // 配置属性
    },
    {
    test: /\.js$/,
    use: [
      'custom-loader1'
    ],
},

// 输出结果
loader pitch1
loader pitch
我的loader
我的loader1

enforce可选的值有pre和post.
指定loader种类。没有值表示是普通loader
还有一个额外的种类"行内loader",loader被应用在import/require行内.

在Pitching和Normal它们的执行顺序分别是:
post, inline, normal, pre;
pre, normal, inline, post;

  • 同步和异步loader

什么是同步的Loader呢?

默认创建的Loader就是同步的Loader;
这个Loader必须通过return或者this.callback来返回结果,交给下一个loader来处理;
通常在有错误的情况下,我们会使用this.callback;

module.exports = function(content, map, meta) {
    console.log('我的loader');
    // return content;
    this.callback(null, content);
}

异步loader

什么是异步的Loader呢?
有时候我们使用Loader时会进行一些异步的操作;
我们希望在异步操作完成后,再返回这个loader处理的结果;
这个时候我们就要使用异步的Loader了;

module.exports = function(content, map, meta) {
    console.log('我的loader')
    const callback = this.async();
    setTimeout(() => {
        callback(null, content);
    }, 2000)
}

  • loader中参数获取以及校验
// 参数校验的库
npm install --save-dev schema-utils 

const { validate } = require('schema-utils');

// 定义校验规则
const schema = {
    type: 'object',
    properties: {
        params: {
            type: 'string',
            description: "参数是string"
      },
    },
  };

module.exports = function(content, map, meta) {
    console.log('我的loader');
    // 获取参数
    const op = this.getOptions();
    console.log(op);
    // 参数校验
    validate(schema, op, {name: "custom-loader"});
    return content;
}

// webpack.config.json配置
module.exports = {
  module: 
    rules: [{
        test: /\.js$/,
        use: [
          {
            loader : 'custom-loader',
            options: {
              params: "name"
            }
          },
        ]
      }]
  },
  resolveLoader: {
    modules: ['node_modules', path.resolve(__dirname, '../src/loader/')],
  },
};

自定义插件

自定义插件

webpack打包过程分析

  • 打包时间分析

利用speed-measure-webpack-plugin插件分析webpack打包时间

  • 打包后文件分析

利用webpack-bundle-analyzer插件分析打包后的文件

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值