Webpack

1. Webpack入门

1. Webpack的安装

webpack的安装目前分为两个:webpack、webpack-cli

那么它们是什么关系呢?

1. 执行webpack命令,会执行node_modules下的.bin目录下的webpack;

2. webpack在执行时是依赖webpack-cli的,如果没有安装就会报错;

4. 所以在安装webpack时,我们需要同时安装webpack-cli(第三方的脚手架事实上是没有使用webpack-cli的,而是类似于自己的vue-service-cli的东西)

npm install webpack webpack-cli –g # 全局安装
npm install webpack webpack-cli –D # 局部安装

2. webpack默认打包

通过webpack进行打包,之后运行打包之后的代码

步骤:

1. 在目录下直接执行 webpack 命令

2. 生成一个dist文件夹,里面存放一个main.js的文件,就是我们打包之后的文件:这个文件中的代码被压缩和丑化了;

webpack是如何确定我们的入口的呢?
事实上,当我们运行webpack时,webpack会查找当前目录下的 src/index.js作为入口;
所以,如果当前项目中没有存在src/index.js文件,那么会报错;

3. Webpack配置文件

在根目录下创建一个webpack.config.js文件,来作为webpack的配置文件

配置文件改名: webpack --config wk.config.js

或者packetage.json修改脚本: “build”: “webpack --config wk.config.js”
避免每次都要对源码编译

4. Webpack依赖图

webpack到底是如何对我们的项目进行打包的呢?
事实上webpack在处理应用程序时,它会根据命令或者配置文件找到入口文件;
从入口开始,会生成一个 依赖关系图,这个依赖关系图会包含应用程序中所需的所有模块(如.js文件、css文件、图片、字体等);
然后遍历图结构,打包一个个模块(根据文件的不同使用不同的loader来解析);webpack和webpack-cli

2. Webpack配置和css处理

demo中通过给js创建一个元素并设定样式,打包时发现webpack报错
在这里插入图片描述
上面的错误信息告诉我们需要一个loader来加载这个css文件

1. css-loader的使用

1. loader是什么呢?

  1. ploader 可以用于对模块的源代码进行转换;
  2. p我们可以将css文件也看成是一个模块,我们是通过import来加载这个模块的;
  3. 在加载这个模块时,webpack其实并不知道如何对其进行加载,我们必须制定对应的loader来完成这个功能;

2. 对于上面的报错,告诉我们需要一个可以读取css文件的loader,也就是css-loader

css-loader的安装:npm install css-loader -D

3. css-loader的使用方案

如何使用这个loader来加载css文件呢?有三种方式:

  1. 内联方式;
  2. CLI方式(webpack5中不再使用);
  3. 配置方式;
1. 内联方式

内联方式使用较少,因为不方便管理; p在引入的样式前加上使用的loader,并且使用!分割;
在这里插入图片描述

2. CLI方式

在webpack5的文档中已经没有了–module-bind; p实际应用中也比较少使用,因为不方便管理;

3. loader配置方式
1. 配置方式表示的意思是在我们的webpack.config.js文件中写明配置信息:
  1. module.rules中允许我们配置多个loader(因为我们也会继续使用其他的loader,来完成其他文件的加载);
  2. 这种方式可以更好的表示loader的配置,也方便后期的维护,同时也让你对各个Loader有一个全局的概览;
2. module.rules的配置如下:

rules属性对应的值是一个数组:[Rule]
数组中存放的是一个个的Rule,Rule是一个对象,对象中可以设置多个属性:

1. test属性:用于对 resource(资源)进行匹配的,通常会设置成正则表达式;
2. use属性:对应的值时一个数组:[UseEntry] ü UseEntry是一个对象,可以通过对象的属性来设置一些其他属性
   		loader:必须有一个 loader属性,对应的值是一个字符串;
   		options:可选的属性,值是一个字符串或者对象,值会被传入到loader中;
   		query:目前已经使用options来替代;
   传递字符串(如:use: [ 'style-loader' ])是 loader 属性的简写方式(如:use: [ { loader: 'style-loader'} ]);	
3. loader属性: Rule.use: [ { loader } ] 的简写。

在这里插入图片描述

2. style-loader

1. 可以通过css-loader来加载css文件,但是会发现这个css在我们的代码中并没有生效(页面没有效果)

因为css-loader只是负责将.css文件进行解析,并不会将解析之后的css插入到页面中;
如果我们希望再完成插入style的操作,那么我们还需要另外一个loader,就是style-loader;

npm install style-loader -D

2. 配置style-loader

  1. 在配置文件中,添加style-loader;

  2. 注意:因为loader的执行顺序是从右向左(或者说从下到上,或者说从后到前的),所以我们需要将styleloader写到css-loader的前面;
    在这里插入图片描述

  3. 重新执行编译npm run build,可以发现打包后的css已经生效了:
    当前目前我们的css是通过页内样式的方式添加进来的;
    还可以通过将css抽取到单独的文件中,并且进行压缩等操作;

3. 处理less文件

1. 通过less工具转换

less、sass等编写的css需要通过工具转换成普通的css;

可以使用less工具来完成它的编译转换:npm install less -D
执行如下命令:npx less ./src/css/title.less > title.css

2. 通过less-loader处理

npm install less-loader -D

配置webpack.config.js:
在这里插入图片描述
执行npm run build
less就可以自动转换成css,并且页面也会生效了

4. 浏览器兼容性

开发中,浏览器的兼容性问题,我们应该如何去解决和处理?(这里指的兼容性是针对不同的浏览器支持的特性:比如css特性、js语法,之间的兼容性;)

在很多的脚手架配置中,都能看到类似于这样的配置信息:
这里的百分之一,就是指市场占有率

> 1%
last 2 versions
not dead

查询浏览器的市场占有率: https://caniuse.com/usage-table

5. 认识browserslist工具

Browserslist是一个在不同的前端工具之间,共享目标浏览器和Node.js版本的配置
在这里插入图片描述
当我们编写配置后,这些工具会根据我们的配置来获取相关的浏览器信息,以方便决定是否需要进行兼容性的支持
在这里插入图片描述

1. Browserslist编写规则一:

在这里插入图片描述

2. Browserslist编写规则二:

在这里插入图片描述

3. 命令行使用browserslist

可以直接通过命令来查询某些条件所匹配到的浏览器:
npx browserslist “>1%, last 2 version, not dead”

4. 配置browserslist

1. 两种方案:

方案一:在package.json中配置;
在这里插入图片描述

方案二:单独的一个配置文件.browserslistrc文件;
在这里插入图片描述

2. 默认配置和条件关系

如果没有配置,那么也会有一个默认配置:
在这里插入图片描述
多个条件之间的关系
在这里插入图片描述

6. PostCSS工具

1. 什么是PostCSS呢?

  1. PostCSS是一个通过JavaScript来转换样式的工具; p这个工具可以帮助我们进行一些CSS的转换和适配,比如自动添加浏览器前缀、css样式的重置;
  2. 但是实现这些工具,我们需要借助于PostCSS对应的插件;

2. 如何使用PostCSS呢?

主要就是两个步骤:
第一步:查找PostCSS在构建工具中的扩展,比如webpack中的postcss-loader;
第二步:选择可以添加你需要的PostCSS相关的插件;

3. 命令行使用postcss

1. 手动使用

需要单独安装一个工具postcss-cli
npm install postcss postcss-cli -D

编写一个需要添加前缀的css:
因为我们需要添加前缀,所以要安装autoprefixer:
npm install autoprefixer -D

直接使用使用postcss工具,并且制定使用autoprefixer:
npx postcss --use autoprefixer -o end.css ./src/css/style.css
在这里插入图片描述

2. postcss-loader

在webpack中使用postcss就是使用postcss-loader来处理的;
npm install postcss-loader -D

修改加载css的loader:(注意:因为postcss需要有对应的插件才会起效果,所以我们需要配置它的plugin;)
在这里插入图片描述
单独的postcss配置文件
也可以将这些配置信息放到一个单独的文件中进行管理:
在根目录下创建postcss.config.js
在这里插入图片描述

3. postcss-preset-env

事实上,在配置postcss-loader时,我们配置插件并不需要使用autoprefixer
使用另外一个插件:postcss-preset-env

postcss-preset-env也是一个postcss的插件,它可以帮助我们将一些现代的CSS特性,转成大多数浏览器认识的CSS,并且会根据目标浏览器或者运行时环 境添加所需的polyfill;也包括会自动帮助我们添加autoprefixer(所以相当于已经内置了autoprefixer);

使用:
安装:npm install postcss-preset-env -D
直接修改掉之前的autoprefixer即可:
在这里插入图片描述
注意:我们在使用某些postcss插件时,也可以直接传入字符串
在这里插入图片描述
postcss-preset-env使用例子:使用十六进制的颜色时设置了8位;某些浏览器可能不认识这种语法,最好可以转成RGBA的形式;但是autoprefixer是不会帮助我们转换的;而postcss-preset-env就可以完成这样的功能;

3. 加载和处理其他资源

1. file-loader

1. file-loader的使用

要处理jpg、png等格式的图片,我们也需要有对应的loader:file-loader

file-loader的作用就是帮助我们处理import/require()方式引入的一个文件资源,并且会将它放到我们输出的文件夹中;

安装file-loader:npm install file-loader -D
配置处理图片的Rule:
在这里插入图片描述

2. 文件的名称规则

几个最常用的placeholder:

[ext]: 处理文件的扩展名;
[name]:处理文件的名称;
[hash]:文件的内容,使用MD4的散列函数处理,生成的一个128位的hash值(32个十六进制);
[contentHash]:在file-loader中和[hash]结果是一致的(在webpack的一些其他地方不一样,后面会讲到);
[hash:<length>]:截图hash的长度,默认32个字符太长了;
[path]:文件相对于webpack配置文件的路径;

设置文件名称
在这里插入图片描述
可以通过outputPath来设置输出的文件夹;
在这里插入图片描述

2. url-loader

1. 使用

url-loader和file-loader的工作方式是相似的,但是可以将较小的文件,转成base64的URI。

安装url-loader:npm install url-loader -D
在这里插入图片描述
显示结果是一样的,并且图片可以正常显示;
但是在dist文件夹中,我们会看不到图片文件:
这是因为我的两张图片的大小分别是38kb和295kb;
默认情况下url-loader会将所有的图片文件转成base64编码

2. url-loader的limit

可以限制哪些大小的图片转换和不转换
url-loader有一个options属性limit,可以用于设置转换的限制;
下面的代码38kb的图片会进行base64编码,而295kb的不会;
在这里插入图片描述

3. asset module type

1. 介绍

在webpack5之前,加载这些资源我们需要使用一些loader,比如raw-loader 、url-loader、file-loader;
在webpack5之后,我们可以直接使用资源模块类型(asset module type),来替代上面的这些loader;

资源模块类型(asset module type),通过添加 4 种新的模块类型,来替换所有这些 loader: 
passet/resource 发送一个单独的文件并导出 URL。之前通过使用 file-loader 实现;
asset/inline 导出一个资源的 data URI。之前通过使用 url-loader 实现;
asset/source 导出资源的源代码。之前通过使用 raw-loader 实现;
asset 在导出一个 data URI 和发送一个单独的文件之间自动选择。之前通过使用 url-loader,并且配置资源体积限制实现;

2. 使用

在这里插入图片描述

如何可以自定义文件的输出路径和文件名呢?
方式一:修改output,添加assetModuleFilename属性;
在这里插入图片描述

方式二:在Rule中,添加一个generator属性,并且设置filename;
在这里插入图片描述

3. url-loader的limit效果

在这里插入图片描述

4. 加载字体文件

如果我们需要使用某些特殊的字体或者字体图标,那么我们会引入很多字体相关的文件,这些文件的处理也是一样
的。
在这里插入图片描述
这个时候打包会报错,因为无法正确的处理eot、ttf、woff等文件:
我们可以选择使用file-loader来处理,也可以选择直接使用webpack5的资源模块类型来处理;
在这里插入图片描述

4. 认识Plugin

Loader是用于特定的模块类型进行转换;
Plugin可以用于执行更加广泛的任务,比如打包优化、资源管理、环境变量注入等;
在这里插入图片描述

1. CleanWebpackPlugin

每次修改了一些配置,重新打包时,都需要手动删除dist文件夹,我们可以借助于一个插件来帮助我们完成,这个插件就CleanWebpackPlugin

先安装这个插件:npm install clean-webpack-plugin -D
在插件中配置:
在这里插入图片描述

2. HtmlWebpackPlugin

1. 使用

我们的HTML文件是编写在根目录下的,而最终打包的dist文件夹中是没有index.html文件的。
在进行项目部署的时,必然也是需要有对应的入口文件index.html;
所以我们也需要对index.html进行打包处理;

对HTML进行打包处理我们可以使用另外一个插件:HtmlWebpackPlugin;

npm install html-webpack-plugin -D

在这里插入图片描述

2. 生成的index.html分析

打包后现在自动在dist文件夹中,生成了一个index.html的文件:
该文件中也自动添加了我们打包的bundle.js文件;
在这里插入图片描述
这个文件是如何生成的呢?
默认情况下是根据ejs的一个模板来生成的;
在html-webpack-plugin的源码中,有一个default_index.ejs模块;

3. 自定义HTML模板

如果我们想在自己的模块中加入一些比较特别的内容:
比如添加一个noscript标签,在用户的JavaScript被关闭时,给予响应的提示;
比如在开发vue或者react项目时,我们需要一个可以挂载后续组件的根标签

这个我们需要一个属于自己的index.html模块:
在这里插入图片描述

4. 自定义模板数据填充

上面有类似这样的语法<% 变量 %>,这个是EJS模块填充数据的方式

在配置HtmlWebpackPlugin时,我们可以添加如下配置:
template:指定我们要使用的模块所在的路径;
title:在进行htmlWebpackPlugin.options.title读取时,就会读到该信息;

在这里插入图片描述

3. DefinePlugin

1. 介绍

这个时候编译还是会报错,因为在我们的模块中还使用到一个BASE_URL的常量:
在这里插入图片描述
这是因为在编译template模块时,有一个BASE_URL:

; 但是我们并没有设置过这个常量值,所以会出现没有定义的错误; n 这个时候我们可以使用DefinePlugin插件; #### 2. 使用 DefinePlugin允许在编译时创建配置的全局常量,是一个webpack内置的插件(不需要单独安装): ![在这里插入图片描述](https://img-blog.csdnimg.cn/4bdfd95a5bf14e188664d411d463ab6c.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAY3hjeGN4Y2NjY2Nj,size_20,color_FFFFFF,t_70,g_se,x_16) 这个时候,编译template就可以正确的编译了,会读取到BASE_URL的值; ### 4. CopyWebpackPlugin 在vue的打包过程中,如果我们将一些文件放到public的目录下,那么这个目录会被复制到dist文件夹中。 这个复制的功能,我们可以使用CopyWebpackPlugin来完成;

安装CopyWebpackPlugin插件:
npm install copy-webpack-plugin -D

接下来配置CopyWebpackPlugin即可:
复制的规则在patterns中设置;

from:设置从哪一个源中开始复制;
to:复制到的位置,可以省略,会默认复制到打包的目录下;
globOptions:设置一些额外的选项,其中可以编写需要忽略的文件:
	.DS_Store:mac目录下回自动生成的一个文件;
	index.html:也不需要复制,因为我们已经通过HtmlWebpackPlugin完成了index.html的生成;

在这里插入图片描述

4. Mode配置

Mode配置选项,可以告知webpack使用响应模式的内置优化:
默认值是production(什么都不设置的情况下);
可选值有:‘none’ | ‘development’ | ‘production’;在这里插入图片描述
在这里插入图片描述

4. 模块化原理和source-map

1. Webpack的模块化

Webpack打包的代码,允许我们使用各种各样的模块化,但是最常用的是CommonJS、ES Module。

包括如下原理:
CommonJS模块化实现原理;
ES Module实现原理;
CommonJS加载ES Module的原理;
ES Module加载CommonJS的原理;

1. CommonJS模块化实现原理

打包报这个警告的话
在这里插入图片描述
执行:nom install html-webpack-plugin@next
不会再报

默认打包为eval
在这里插入图片描述
切换为source-map
在这里插入图片描述

配置文件:

module.exports = {
  // 当mode设置为development时devtool默认值为eval
  mode: "development",
  entry: "./src/index.js",
  devtool: "source-map",
  output: {
    filename: "js/bundle.js",
    // 必须是一个绝对路径
    path: path.resolve(__dirname, "./build"),
    // assetModuleFilename: "img/[name].[hash:6][ext]"
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      title: "coderwhy webpack"
    })
  ]
}

阅读打包代码bundle.js:

// 定义了一个对象
// 模块的路径(key): 被打包的函数(value)
var __webpack_modules__ = {
  "./src/js/format.js":
    (function (module) {
      const dateFormat = (date) => {
        return "2020-12-12";
      }
      const priceFormat = (price) => {
        return "100.00";
      }

      // 将我们要导出的变量, 放入到module对象中的exports对象
      module.exports = {
        dateFormat,
        priceFormat
      }
    })
}

// 定义一个对象, 作为加载模块的缓存
var __webpack_module_cache__ = {};

// 是一个函数, 当我们加载一个模块时, 都会通过这个函数来加载
function __webpack_require__(moduleId) {
  // 1.判断缓存中是否已经加载过
  if (__webpack_module_cache__[moduleId]) {
    return __webpack_module_cache__[moduleId].exports;
  }

  // 2.给module变量和__webpack_module_cache__[moduleId]“(moduleId的value)”赋值了同一个对象
  // 使用__webpack_module_cache__[moduleId]的方式是为了同样的对象共用一个内存空间,无论是什么时候改值,数据共享
  var module = __webpack_module_cache__[moduleId] = { exports: {} };

  // 3.加载执行模块,传给顶层的__webpack_modules__
  // 这里传入的module.exports是为了把__webpack_modules__ return的缓存到__webpack_module_cache__里面,以便下一次读取
  __webpack_modules__[moduleId](module, module.exports, __webpack_require__);

  // 4.导出module.exports {dateFormat: function, priceForamt: function}
  return module.exports;
}

// 具体开始执行代码逻辑
// !: 立即执行(无需括号),实际是把他变成一个表达式
!function () {
  // 1.加载./src/js/format.js
  // key-value取
  const { dateFormat, priceFormat } = __webpack_require__("./src/js/format.js");
  console.log(dateFormat("abc"));
  console.log(priceFormat("abc"));
}();

2. ES Module实现原理

阅读打包代码bundle.js:

// 1.定义了一个对象, 对象里面放的是我们的模块映射
var __webpack_modules__ = {
  "./src/es_index.js":
    (function (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
      // 调用r的目的是记录一个__esModule -> true
      __webpack_require__.r(__webpack_exports__);

      // _js_math__WEBPACK_IMPORTED_MODULE_0__ == exports
      var _js_math__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/js/math.js");

      // 通过definition去调用
      console.log(_js_math__WEBPACK_IMPORTED_MODULE_0__.mul(20, 30));
      console.log(_js_math__WEBPACK_IMPORTED_MODULE_0__.sum(20, 30));
    }),
  "./src/js/math.js":
    (function (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
      __webpack_require__.r(__webpack_exports__);

      // 调用了d函数: 给exports设置了一个代理definition
      // exports对象中本身是没有对应的函数
      __webpack_require__.d(__webpack_exports__, {
        "sum": function () { return sum; },
        "mul": function () { return mul; }
      });

      const sum = (num1, num2) => {
        return num1 + num2;
      }
      const mul = (num1, num2) => {
        return num1 * num2;
      }
    })
};

// 2.模块的缓存
var __webpack_module_cache__ = {};

// 3.require函数的实现(加载模块)
function __webpack_require__(moduleId) {
  if (__webpack_module_cache__[moduleId]) {
    return __webpack_module_cache__[moduleId].exports;
  }
  var module = __webpack_module_cache__[moduleId] = {
    exports: {}
  };
  __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
  return module.exports;
}

!function () {
  // __webpack_require__这个函数对象添加了一个属性: d -> 值function
  __webpack_require__.d = function (exports, definition) {
    // 用存取属性描述符取出
    for (var key in definition) {
      if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
        Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
      }
    }
  };
}();


!function () {
  // __webpack_require__这个函数对象添加了一个属性: o -> 值function
  __webpack_require__.o = function (obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }
}();

// 打一个模块标记
!function () {
  // __webpack_require__这个函数对象添加了一个属性: r -> 值function
  __webpack_require__.r = function (exports) {
    if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
      // 告诉exports它是一个模块
      Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
    }
    Object.defineProperty(exports, '__esModule', { value: true });
  };
}();


__webpack_require__("./src/es_index.js");

3. CommonJS加载ES Module的原理

无论是es还是common都支持解构导出
es导出,common导入

阅读打包代码bundle.js:

var __webpack_modules__ = ({
  "./src/index.js":
    (function (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
      "use strict";
      __webpack_require__.r(__webpack_exports__);
      var _js_format__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/js/format.js");
      var _js_format__WEBPACK_IMPORTED_MODULE_0___default = __webpack_require__.n(_js_format__WEBPACK_IMPORTED_MODULE_0__);
      
      // es module导出内容, CommonJS导入内容
      const math = __webpack_require__("./src/js/math.js");

      // CommonJS导出内容, es module导入内容
      console.log(math.sum(20, 30));
      console.log(math.mul(20, 30));
      console.log(_js_format__WEBPACK_IMPORTED_MODULE_0___default().dateFormat("aaa"));
      console.log(_js_format__WEBPACK_IMPORTED_MODULE_0___default().priceFormat("bbb"));
    }),
  "./src/js/format.js":
    (function (module) {
      const dateFormat = (date) => {
        return "2020-12-12";
      }
      const priceFormat = (price) => {
        return "100.00";
      }
      module.exports = {
        dateFormat,
        priceFormat
      }
    }),

  "./src/js/math.js":
    (function (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
      
      __webpack_require__.r(__webpack_exports__);
      __webpack_require__.d(__webpack_exports__, {
        "sum": function () { return sum; },
        "mul": function () { return mul; }
      });
      const sum = (num1, num2) => {
        return num1 + num2;
      }

      const mul = (num1, num2) => {
        return num1 * num2;
      }
    })
});

var __webpack_module_cache__ = {};

// The require function
function __webpack_require__(moduleId) {
  // Check if module is in cache
  if (__webpack_module_cache__[moduleId]) {
    return __webpack_module_cache__[moduleId].exports;
  }
  // Create a new module (and put it into the cache)
  var module = __webpack_module_cache__[moduleId] = {
    // no module.id needed
    // no module.loaded needed
    exports: {}
  };

  // Execute the module function
  __webpack_modules__[moduleId](module, module.exports, __webpack_require__);

  // Return the exports of the module
  return module.exports;
}

!function () {
  // getDefaultExport function for compatibility with non-harmony modules
  __webpack_require__.n = function (module) {
    // 判断是不是esModule
    var getter = module && module.__esModule ?
      function () { return module['default']; } :
      function () { return module; };
    __webpack_require__.d(getter, { a: getter });
    return getter;
  };
}();

/* webpack/runtime/define property getters */
!function () {
  // define getter functions for harmony exports
  __webpack_require__.d = function (exports, definition) {
    for (var key in definition) {
      if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
        Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
      }
    }
  };
}();

/* webpack/runtime/hasOwnProperty shorthand */
!function () {
  __webpack_require__.o = function (obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }
}();

/* webpack/runtime/make namespace object */
!function () {
  // define __esModule on exports
  __webpack_require__.r = function (exports) {
    if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
      Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
    }
    Object.defineProperty(exports, '__esModule', { value: true });
  };
}();

__webpack_require__("./src/index.js");

4. ES Module加载CommonJS的原理

2. source-map

1. source-map作用

我们的代码通常运行在浏览器上时,是通过打包压缩的:
也就是真实跑在浏览器上的代码,和我们编写的代码其实是有差异的;

比如ES6的代码可能被转换成ES5; 
比如对应的代码行号、列号在经过编译后肯定会不一致;
比如代码进行丑化压缩时,会将编码名称等修改;
比如我们使用了TypeScript等方式编写的代码,最终转换成JavaScript;

但是当代码报错需要调试时(debug),调试转换后的代码是很困难的

那么如何可以调试这种转换后不一致的代码呢?答案就是source-map

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

2. 使用source-map

第一步:根据源文件,生成source-map文件,webpack在打包时,可以通过配置生成source-map(这个参数生成的是完整的source-map)
在这里插入图片描述

第二步:在转换后的代码,最后添加一个注释,它指向sourcemap;
//# sourceMappingURL=common.bundle.js.map
在这里插入图片描述

浏览器会根据我们的注释,查找响应的source-map,并且根据source-map还原我们的代码(最原始的代码),方便进行调试。

在Chrome中,我们可以按照如下的方式打开source-map:
在这里插入图片描述

3. 分析source-map

最初source-map生成的文件带下是原始文件的10倍,第二版减少了约50%,第三版又减少了50%,所以目前一个133kb的文件,最终的source-map的大小大概在300kb。

目前的source-map长什么样子呢?

version:当前使用的版本,也就是最新的第三版; 
sources:从哪些文件转换过来的source-map和打包的代码(最初始的文件);
names:转换前的变量和属性名称(因为目前使用的是development模式,所以不需要保留转换前的名称,如果使用production模式/丑化会做一个备份);
mappings:source-map用来和源文件映射的信息(比如位置信息等),一串base64 VLQ(veriable length quantity可变长度值)编码;
file:打包后的文件(浏览器加载的文件);
sourceContent:转换前的具体代码信息(和sources是对应的关系);
sourceRoot:所有的sources相对的根目录;

4. source-map文件

在这里插入图片描述

5. 生成source-map

如何在使用webpack打包的时候,生成对应的source-map呢?

webpack为我们提供了非常多的选项(目前是26个),来处理source-map;
https://webpack.docschina.org/configuration/devtool/
选择不同的值,生成的source-map会稍微有差异,打包的过程也会有性能的差异,可以根据不同的情况进行选择;

下面几个值不会生成source-map文件(devtool选项)

在这里插入图片描述
none直接不写,否则打包报错

6. eval的效果

可以具体定位到是哪个文件(源文件)的哪里有错误
在这里插入图片描述

7. source-map值

source-map: 生成一个独立的source-map文件,并且在bundle文件中有一个注释,指向source-map文件;

bundle文件中有如下的注释:开发工具会根据这个注释找到source-map文件,并且解析;
//# sourceMappingURL=bundle.js.map
在这里插入图片描述

8. eval-source-map值

eval-source-map:会生成sourcemap,但是不生成source-map文件,source-map是以DataUrl添加到eval函数的后面
在这里插入图片描述
如果值带eval,那么生成的source-map都在eval里面

9. inline-source-map值

inline-source-map:会生成sourcemap,但是source-map是以DataUrl添加到bundle文件的后面
在这里插入图片描述

10. cheap-source-map

会生成sourcemap,但是会更加高效一些(cheap低开销),因为它没有生成列映射(Column Mapping)
因为在开发中,我们只需要行信息通常就可以定位到错误了
在这里插入图片描述

11. cheap-module-source-map值(推荐)

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

这里有一个很模糊的概念:对源自loader的sourcemap处理会更好,官方也没有给出很好的解释

其实是如果loader对我们的源码进行了特殊的处理,比如babel

这里使用了babel-loader
在这里插入图片描述

12. cheap-source-map和cheap-module-source-map

当源代码有一些被babel-loader处理了,如果不是使用cheap-module-source-map,那么打包的是经过loader处理后的代码,加上module就是打包源代码,更方便调试
在这里插入图片描述

13. hidden-source-map值

会生成sourcemap文件,但是不会对source-map文件进行引用;
相当于删除了打包文件中对sourcemap的引用注释;
// 被删除掉的
//# sourceMappingURL=bundle.js.map

如果我们手动添加进来,那么sourcemap就会生效了

一般发布条件下生成这个值

14. nosources-source-map值

nosources-source-map:
会生成sourcemap,但是生成的sourcemap只有错误信息的提示,不会生成源代码文件;
在这里插入图片描述

15. 多个值的组合

事实上,webpack提供给我们的26个值,是可以进行多组合的。

  1. 组合的规则如下:
1. inline-|hidden-|eval:三个值时三选一
2. nosources:可选值
3. cheap可选值,并且可以跟随module的值:
        [inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map
  1. 那么在开发中,最佳的实践是什么呢?
4. 开发阶段:推荐使用 source-map或者cheap-module-source-map,这分别是vue和react使用的值,可以获取调试信息,方便快速开发;react会判断环境
5. 测试阶段:推荐使用 source-map或者cheap-module-source-map
           测试阶段我们也希望在浏览器下看到正确的错误提示;
6. 发布阶段:false、缺省值(不写)[source-map]可以还原源代码,不安全

4. Babel的深入解析

1. 认识babel

Babel是一个工具链,主要用于旧浏览器或者缓解中将ECMAScript2015+代码转换为向后兼容版本的JavaScript;
包括:语法转换、源代码转换、Polyfill实现目标缓解缺少的功能等;
在这里插入图片描述

2.Babel的使用

1 .命令行使用

babel本身可以作为一个独立的工具(和postcss一样),不和webpack等构建工具配置来单独使用。

  1. 需要安装如下库: npm install @babel/cli @babel/core
    @babel/core:babel的核心代码,必须安装;
    @babel/cli:可以让我们在命令行使用babel;
  2. 使用babel来处理我们的源代码:npx babel src --out-dir dist
    src:是源文件的目录;
    –out-dir:指定要输出的文件夹dist;

2. 插件使用

比如我们需要转换箭头函数,那么我们就可以使用箭头函数转换相关的插件:
npm install @babel/plugin-transform-arrow-functions -D

npx babel src --out-dir dist --plugins=@babel/plugin-transform-arrow-functions

查看转换后的结果:我们会发现 const 并没有转成 var
这是因为 plugin-transform-arrow-functions,并没有提供这样的功能;
我们需要使用 plugin-transform-block-scoping 来完成这样的功能;
npm install @babel/plugin-transform-block-scoping -D

npx babel src --out-dir dist --plugins=@babel/plugin-transform-block-scoping,@babel/plugin-transform-arrow-functions

3. Babel的预设preset

但是如果要转换的内容过多,一个个设置是比较麻烦的,我们可以使用预设(preset):
安装@babel/preset-env预设:npm install @babel/preset-env -D
执行如下命令:npx babel src --out-dir dist --presets=@babel/preset-env

3. Babel的底层原理

1. babel原理

事实上我们可以将babel看成就是一个编译器。
Babel编译器的作用就是将我们的源代码,转换成浏览器可以直接识别的另外一段源代码;

Babel也拥有编译器的工作流程:
解析阶段(Parsing)
转换阶段(Transformation)
生成阶段(Code Generation)

https://github.com/jamiebuilds/the-super-tiny-compiler

2. babel编译器执行原理

在这里插入图片描述
中间产生的代码

const name = "coderwhy";
const foo = (name) => console.log(name);
foo(name);

4. babel-loader

1. 安装和使用

通常会在构建工具(比如webpack)中通过配置babel来对其进行使用的

安装相关的依赖:(如果之前已经安装了@babel/core,那么这里不需要再次安装)
npm install babel-loader @babel/core

设置babel规则:
在这里插入图片描述

指定使用的插件(必须使用才能生效):
在这里插入图片描述

2. babel-preset

如果我们一个个去安装使用插件,那么需要手动来管理大量的babel插件,我们可以直接给webpack提供一个preset,webpack会根据我们的预设来加载对应的插件列表,并且将其传递给babel

比如常见的预设有三个:
env
react
TypeScript

在这里插入图片描述
安装preset-env:npm install @babel/preset-env

5. 设置目标浏览器 browserslist

我们最终打包的JavaScript代码,是需要跑在目标浏览器上的,那么如何告知babel我们的目标浏览器呢?
browserslist工具
target属性

1. browserslist

在这里插入图片描述

2. target

在这里插入图片描述

如果两个同时配置了,哪一个会生效呢?

配置的targets属性会覆盖browserslist;
但是在开发中,更推荐通过browserslist来配置,因为类似于postcss工具,也会使用browserslist,进行统一浏览器的适配;

6. Stage-X的preset

1. 了解TC39

在这里插入图片描述

2. Babel的Stage-X设置

在babel7之前(比如babel6中),我们会经常看到这种设置方式:
它表达的含义是使用对应的 babel-preset-stage-x 预设;
但是从babel7开始,已经不建议使用了,建议使用preset-env来设置;
在这里插入图片描述

7. Babel的配置文件

babel给我们提供了两种配置文件的编写:
babel.config.json(或者.js,.cjs,.mjs)文件;
.babelrc.json(或者.babelrc,.js,.cjs,.mjs)文件;

区别:
.babelrc.json:早期使用较多的配置方式,但是对于配置Monorepos项目是比较麻烦的;
babel.config.json(babel7):可以直接作用于Monorepos项目的子包,更加推荐;
在这里插入图片描述

8. polyfill

1. 认识polyfill

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

2. 使用polyfill

babel7.4.0之前,可以使用 @babel/polyfill的包,但是该包现在已经不推荐使用了:
在这里插入图片描述
babel7.4.0之后,可以通过单独引入core-js和regenerator-runtime来完成polyfill的使用:
npm install core-js regenerator-runtime --save
在这里插入图片描述

3. 认识Plugin-transform-runtime(了解)

我们使用的polyfill,默认情况是添加的所有特性都是全局的
如果我们正在编写一个工具库,这个工具库需要使用polyfill;别人在使用我们工具时,工具库通过polyfill添加的特性,可能会污染它们的代码;
所以,当编写工具时,babel更推荐我们使用一个插件: @babel/plugin-transform-runtime来完成polyfill的功能;

1. 使用Plugin-transform-runtime

安装 @babel/plugin-transform-runtime:
npm install @babel/plugin-transform-runtime -D

使用plugins来配置babel.config.js:
在这里插入图片描述

注意:如果使用了corejs3,需要安装对应的库:
在这里插入图片描述

9. 配置babel.config.js

需要在babel.config.js文件中进行配置,给preset-env配置一些属性:

1. useBuiltIns:设置以什么样的方式来使用polyfill; 
2. corejs:设置corejs的版本,目前使用较多的是3.x的版本,比如我使用的是3.8.x的版本;
3. 另外corejs可以设置是否对提议阶段的特性进行支持;设置 proposals属性为true即可;

1. useBuiltIns属性设置

useBuiltIns属性有三个常见的值:
第一个值:false
打包后的文件不使用polyfill来进行适配;
并且这个时候是不需要设置corejs属性的;

第二个值:usage
会根据源代码中出现的语言特性,自动检测所需要的polyfill;
这样可以确保最终包里的polyfill数量的最小化,打包的包相对会小一些;
可以设置corejs属性来确定使用的corejs的版本;
在这里插入图片描述
第三个值:entry
如果我们依赖的某一个库本身使用了某些polyfill的特性,但是因为我们使用的是usage,所以之后用户浏览器可能会报错;
所以,如果你担心出现这种情况,可以使用 entry;
并且需要在入口文件中添加 `import ‘core-js/stable’; import ‘regenerator-runtime/runtime’;
这样做会根据 browserslist 目标导入所有的polyfill,但是对应的包也会变大;
在这里插入图片描述

10. 其他编译器

1. React的jsx支持

在我们编写react代码时,react使用的语法是jsx,jsx是可以直接使用babel来转换的。
对react jsx代码进行处理需要如下的插件:
@babel/plugin-syntax-jsx p@babel/plugin-transform-react-jsx @babel/plugin-transform-react-display-name
但是开发中,我们并不需要一个个去安装这些插件,我们依然可以使用preset来配置:npm install @babel/preset-react -D
在这里插入图片描述

2. TypeScript的编译

TypeScript代码是需要转换成JavaScript代码

1. ts的compiler

可以通过TypeScript的compiler来转换成JavaScript:
npm install typescript -D

另外TypeScript的编译配置信息我们通常会编写一个tsconfig.json文件:
tsc --init
生成配置文件如下:
在这里插入图片描述
之后我们可以运行 npx tsc来编译自己的ts代码:npx tsc

2. 使用ts-loader

如果我们希望在webpack中使用TypeScript,那么我们可以使用ts-loader来处理ts文件:npm install ts-loader -D

配置ts-loader:
在这里插入图片描述
之后,我们通过npm run build打包即可。

3. 使用babel-loader

Babel是有对TypeScript进行支持;
我们可以使用插件: @babel/tranform-typescript; 但是更推荐直接使用preset:@babel/preset-typescript;

npm install @babel/preset-typescript -D
在这里插入图片描述

4. ts-loader和babel-loader选择
  1. 使用ts-loader(TypeScript Compiler) 来直接编译TypeScript,那么只能将ts转换成js;
    如果我们还希望在这个过程中添加对应的polyfill,那么ts-loader是无能为力的;我们需要借助于babel来完成polyfill的填充功能;
  2. 使用babel-loader(Babel) 来直接编译TypeScript,也可以将ts转换成js,并且可以实现polyfill的功能;
    但是babel-loader在编译的过程中,不会对类型错误进行检测;

那么在开发中,我们如何可以同时保证两个情况都没有问题呢?
我们使用Babel来完成代码的转换,使用tsc来进行类型的检查。

如何可以使用tsc来进行类型的检查呢?
在这里,在scripts中添加了两个脚本,用于类型检查;
我们执行 npm run type-check可以对ts代码的类型进行检测;
我们执行 npm run type-check-watch可以实时的检测类型错误;
在这里插入图片描述

11. ESLint

1. 认识ESLint

什么是ESLint呢?

  1. ESLint是一个静态代码分析工具(Static program analysis,在没有任何程序执行的情况下,对代码进行分析);
  2. ESLint可以帮助我们在项目中建立统一的团队代码规范,保持正确、统一的代码风格,提高代码的可读性、可维护性;
  3. 并且ESLint的规则是可配置的,我们可以自定义属于自己的规则;
  4. 早期还有一些其他的工具,比如JSLint、JSHint、JSCS等,目前使用最多的是ESLint。

2. 使用ESLint

安装:npm install eslint -D
创建配置文件:npx eslint --init
选择想要使用的ESLint:
在这里插入图片描述
执行检测命令:npx eslint ./src/main.js

3. ESLint的文件解析

默认创建的环境如下:

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

4. VSCode的ESLint插件

在这里插入图片描述
实时监听校验

一方面我们可以修改代码来修复错误,另外我们可以通过配置规

格式是: 配置的规则名称:对应的值值可以是数字、字符串、数组: 
       字符串对应有三个值: off、warn、error; 
       数字对应有三个值: 0、1、2(分别对应上面的值); ü
       数组我们可以告知对应的提示以及希望获取到的值:比如 ['error', 'double']

5. VSCode的Prettier插件

ESLint会帮助我们提示错误(或者警告),但是不会帮助我们自动修复:
在开发中我们希望文件在保存时,可以自动修复这些问题;
我们可以选择使用另外一个工具:prettier;
在这里插入图片描述

6. ESLint-Loader的使用

事实上,我们在编译代码的时候,也希望进行代码的eslint检测,这个时候我们就可以使用eslint-loader来完成了:
npm install eslint-loader -D
在这里插入图片描述

12. Webpack中配置vue加载

安装相关的依赖:
npm install vue-loader -D
npm install vue-template-compiler -D
在这里插入图片描述

5. DevServer和HMR

1. 为什么要搭建本地服务器?

目前我们开发的代码,为了运行需要有两个操作:
操作一:npm run build,编译相关的代码;
操作二:通过live server或者直接通过浏览器,打开index.html代码,查看效果;

这个过程经常操作会影响我们的开发效率,我们希望可以做到,当文件发生变化时,可以自动的完成 编译展示

为了完成自动编译,webpack提供了几种可选的方式:
webpack watch mode;
webpack-dev-server;
webpack-dev-middleware

2. webpack实现自动更新的方式

1. Webpack watch

webpack给我们提供了watch模式:
在该模式下,webpack依赖图中的所有文件,只要有一个发生了更新,那么代码将被重新编译;
我们不需要手动去运行 npm run build指令了;

如何开启watch呢?两种方式:
方式一:在导出的配置中,添加 watch: true;
方式二:在启动webpack的命令中,添加 --watch的标识;
这里我们选择方式二,在package.json的 scripts 中添加一个 watch 的脚本:
在这里插入图片描述

2. webpack-dev-server

上面的方式可以监听到文件的变化,但是事实上它本身是没有自动刷新浏览器的功能的:
当然,目前我们可以在VSCode中使用live-server来完成这样的功能;
但是,我们希望在不适用live-server的情况下,可以具备live reloading(实时重新加载)的功能;

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

添加一个新的scripts脚本
在这里插入图片描述
webpack-dev-server 在编译之后不会写入到任何输出文件。而是将 bundle 文件保留在内存中:
事实上webpack-dev-server使用了一个库叫memfs(memory-fs webpack自己写的)

3. webpack-dev-middleware

默认情况下,webpack-dev-server已经帮助我们做好了一切:
· 比如通过express启动一个服务,比如HMR(热模块替换);
· 如果我们想要有更好的自由度,可以使用webpack-dev-middleware;

什么是webpack-dev-middleware?
· webpack-dev-middleware 是一个封装器(wrapper),它可以把webpack 处理过的文件发送到一个server;
· webpack-dev-server 在内部使用了它,然而它也可以作为一个单独的 package 来使用,以便根据需求进行 更多自定义设置;

webpack-dev-middleware的使用
npm install --save-dev express webpack-dev-middleware

在这里插入图片描述
也可以使用koa来搭建服务

3. 模块热替换(HMR)

1. 认识HMR

什么是HMR呢?
HMR的全称是Hot Module Replacement,翻译为模块热替换;
模块热替换是指在 应用程序运行过程中,替换、添加、删除模块,而无需重新刷新整个页面;

HMR通过如下几种方式,来提高开发的速度:

1. 不重新加载整个页面,这样可以保留某些应用程序的状态不丢失; 
2. 只更新需要变化的内容,节省开发的时间; 
3. 修改了css、js源代码,会立即在浏览器更新,相当于直接在浏览器的devtools中直接修改样式;

如何使用HMR呢?
默认情况下,webpack-dev-server已经支持HMR,我们只需要开启即可;
在不开启HMR的情况下,当我们修改了源代码之后,整个页面会自动刷新,使用的是live reloading;

2. 使用

开启HMR:
修改webpack的配置:
在这里插入图片描述
浏览器可以看到如下效果:
在这里插入图片描述
但是你会发现,当我们修改了某一个模块的代码时,依然是刷新的整个页面:
这是因为我们需要去指定哪些模块发生更新时,进行HMR;
在这里插入图片描述

3. 框架的HMR

比如vue开发中,我们使用vue-loader,此loader支持vue组件的HMR,提供开箱即用的体验;
比如react开发中,有React Hot Loader,实时调整react组件(目前React官方已经弃用了,改成使用reactrefresh);

1. React的HMR

使用react-refresh来实现

安装实现HMR相关的依赖:
注意:这里安装@pmmmwh/react-refresh-webpack-plugin,最新的npm安装有bug(建议使用lts版本对 应的npm版本);
npm install -D @pmmmwh/react-refresh-webpack-plugin react-refresh

修改webpack.config.js和babel.config.js文件:
在这里插入图片描述

2. Vue的HMR

Vue的加载我们需要使用vue-loader,而vue-loader加载的组件默认会帮助我们进行HMR的处理。

安装加载vue所需要的依赖:
npm install vue-loader vue-template-compiler -D

配置webpack.config.js:
在这里插入图片描述

4. HMR的原理

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

HMR Socket Server,是一个socket的长连接:

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

HMR的原理图
在这里插入图片描述

4. publicPath

1. output的publicPath

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

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

2. devServer的publicPath

devServer中也有一个publicPath的属性,该属性是指定本地服务所在的文件夹:
它的默认值是 /,也就是我们直接访问端口即可访问其中的资源http://localhost:8080;
如果我们将其设置为了 /abc,那么我们需要通过http://localhost:8080/abc才能访问到对应的打包后的资源; 并且这个时候,我们其中的bundle.js通过 http://localhost:8080/bundle.js也是无法访问的:
所以必须将output.publicPath也设置为 /abc;

官方其实有提到,建议 devServer.publicPath 与 output.publicPath相同

5. devServer的contentBase

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

在index.html中,我们应该如何去引入这个文件呢?
比如代码是这样的:;

但是我们如何让它去查找到这个文件的存在呢? 设置contentBase即可;
当然在devServer中还有一个可以监听contentBase发生变化后重新编译的一个属性:watchContentBase

6. 其他配置

1. hotOnly、host配置

  1. hotOnly是当代码编译失败时,是否刷新整个页面:
    默认情况下当代码编译失败修复后,我们会重新刷新整个页面;
    如果不希望重新刷新整个页面,可以设置hotOnly为true;

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

  3. localhost 和 0.0.0.0 的区别:
    localhost:本质上是一个域名,通常情况下会被解析成127.0.0.1;
    127.0.0.1:回环地址(Loop Back Address),表达的意思其实是我们主机自己发出去的包,直接被自己接收;
    正常的数据库包经常 应用层 - 传输层 - 网络层 - 数据链路层 - 物理层 ; 而回环地址,是在网络层直接就被获取到了,是不会经常数据链路层和物理层的;
    比如我们监听 127.0.0.1时,在同一个网段下的主机中,通过ip地址是不能访问的;
    0.0.0.0:监听IPV4上所有的地址,再根据端口找到不同的应用程序; 比如我们监听 0.0.0.0时,在同一个网段下的主机中,通过ip地址是可以访问的;

7. 其他属性

1. port、open、compress

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

  2. open是否打开浏览器:
    默认值是false,设置为true会打开浏览器;
    也可以设置为类似于 Google Chrome等值;

  3. compress是否为静态文件开启gzip compression(压缩):
    默认值是false,可以设置为true;
    在这里插入图片描述

2. Proxy代理

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

我们可以进行如下的设置:

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

changeOrigin的解析
修改代理请求中的headers中的host属性:
因为我们真实的请求,其实是需要通过 http://localhost:8888来请求的;
但是因为使用了代码,默认情况下它的值时 http://localhost:8000;
在这里插入图片描述

如果我们需要修改,那么可以将changeOrigin设置为true即可;

3. historyApiFallback

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

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

事实上devServer中实现historyApiFallback功能是通过connect-history-api-fallback库的:
可以查看connect-history-api-fallback 文档

8. resolve模块解析

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

  2. webpack能解析三种文件路径:

1. 绝对路径
由于已经获得文件的绝对路径,因此不需要再做进一步解析。

2. 相对路径
在这种情况下,使用 import 或 require 的资源文件所处的目录,被认为是上下文目录;
在 import/require 中给定的相对路径,会拼接此上下文路径,来生成模块的绝对路径;

3. 模块路径
在 resolve.modules中指定的所有目录检索模块;
默认值是 ['node_modules'],所以默认会从node_modules中查找文件;
我们可以通过设置别名(alias)的方式来替换初识模块路径
  1. 确实文件还是文件夹
    如果是一个文件:
    如果文件具有扩展名,则直接打包文件;
    否则,将使用 resolve.extensions选项作为文件扩展名解析;

如果是一个文件夹:
会在文件夹中根据 resolve.mainFiles配置选项中指定的文件顺序查找;
resolve.mainFiles的默认值是 [‘index’];
再根据 resolve.extensions来解析扩展名

  1. extensions和alias配置
    extensions是解析到文件时自动添加扩展名:
    默认值是 [’.wasm’, ‘.mjs’, ‘.js’, ‘.json’]; 所以如果我们代码中想要添加加载 .vue 或者 jsx 或者 ts 等文件时,我们必须自己写上扩展名;
    另一个非常好用的功能是配置别名alias: 特别是当我们项目的目录结构比较深的时候,或者一个文件的路径可能需要 …/…/…/这种路径片段; 我们可以给某些常见的路径起一个别名;
    在这里插入图片描述

7. 环境分离和代码分离

1. 环境分离

1. 如何区分开发环境

目前我们所有的webpack配置信息都是放到一个配置文件中的:webpack.config.js
当配置越来越多时,这个文件会变得越来越不容易维护; 并且某些配置是在开发环境需要使用的,某些配置是在生成环境需要使用的,当然某些配置是在开发和生成环境都会使用的;
所以,我们最好对配置进行划分,方便我们维护和管理;

那么,在启动时如何可以区分不同的配置呢?
方案一:编写两个不同的配置文件,开发和生成时,分别加载不同的配置文件即可;
方式二:使用相同的一个入口配置文件,通过设置参数来区分它们;
在这里插入图片描述

2. 入口文件解析

如果我们的配置文件所在的位置变成了 config 目录,编写入口文件的规则是否应该变成 …/src/index.js呢?
如果我们这样编写,会发现是报错的,依然要写成 ./src/index.js;
这是因为入口文件其实是和另一个属性时有关的 context;

context的作用是用于解析入口(entry point)和加载器(loader):
官方说法:默认是当前路径(默认应该是webpack的启动目录),另外推荐在配置中传入一个值;
在这里插入图片描述

3. 配置文件的分离

可以创建三个文件:
webpack.comm.conf.js
webpack.dev.conf.js
webpack.prod.conf.js

2. 代码分离

主要的目的是将代码分离到不同的bundle中,之后我们可以按需加载,或者并行加载这些文件;
比如默认情况下,所有的JavaScript代码(业务代码、第三方依赖、暂时没有用到的模块)在首页全部都加载,
就会影响首页的加载速度;
代码分离可以分出出更小的bundle,以及控制资源加载优先级,提供代码的加载性能;

Webpack中常用的代码分离有三种:

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

1. 多入口起点

入口起点的含义非常简单,就是配置多入口:比如配置一个index.js和main.js的入口;他们分别有自己的代码逻辑;
在这里插入图片描述

2. 防止重复

1. Entry Dependencies(入口依赖)

假如我们的index.js和main.js都依赖两个库:lodash、dayjs
如果我们单纯的进行入口分离,那么打包后的两个bunlde都有会有一份lodash和dayjs; 事实上我们可以对他们进行共享;
在这里插入图片描述

2. SplitChunks

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

Webpack提供了SplitChunksPlugin默认的配置,我们也可以手动来修改它的配置:
比如默认配置中,chunks仅仅针对于异步(async)请求,我们可以设置为initial或者all;
SplitChunks默认配置
SplitChunks自定义配置
![在这里插入图片描述](https://img-blog.csdnimg.cn/c5c64124022c4b3bb7173c4ed179437d.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAY3hjeGN4Y2NjY2Nj,size_20,color_FFFFFF,t_70,g_se,x_16
在这里插入图片描述

3. 动态导入(dynamic import)

1. 认识

webpack提供了两种实现动态导入的方式:
第一种,使用ECMAScript中的 import() 语法来完成,也是目前推荐的方式;
第二种,使用webpack遗留的 require.ensure,目前已经不推荐使用;

比如我们有一个模块 bar.js: p该模块我们希望在代码运行过程中来加载它(比如判断一个条件成立时加载);因为我们并不确定这个模块中的代码一定会用到,所以最好拆分成一个独立的js文件;
这样可以保证不用到该内容时,浏览器不需要加载和处理该文件的js代码;这个时候我们就可以使用动态导入;

注意:使用动态导入bar.js:在webpack中,通过动态导入获取到一个对象; 真正导出的内容,在改对象的default属性中,所以我们需要做一个简单的解构

2. 动态导入的文件命名

通过 chunkFilename 属性来命名;
在这里插入图片描述
默认情况下我们获取到的 [name] 是和id的名称保持一致的
如果我们希望修改name的值,可以通过magic comments(魔法注释)的方式;
在这里插入图片描述

3. 代码的懒加载

动态import使用最多的一个场景是懒加载(比如路由懒加载):
封装一个component.js,返回一个component对象;
我们可以在一个按钮点击时,加载这个对象;
在这里插入图片描述

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值